package tcg.scada.da;
import java.util.Calendar;
import tcg.common.LoggerManager;
import tcg.common.util.ExpressionParser;
import tcg.scada.cos.CosDpQualityEnum;
import tcg.scada.cos.CosDpValueStruct;
import tcg.scada.cos.CosDpValueTypeEnum;
import tcg.scada.cos.CosDpValueUnion;
/**
* <p>
* Implementation of real datapoint.
* </p>
*
* <p>
* Expected behaviour for real datapoint:
* <ul>
* <li>Source value: source value from subsystem/poller</li>
* <li>Source quality: source quality from subsystem/poller</li>
* <li>Source timestamp: source timestamp if applicable, otherwise timestamp of
* last set-source-value</li>
* <li>Output value: source value if not inhibited/overridden</li>
* <li>Output quality: internal calculation</li>
* <li>Output timestamp: source timestamp if not inhibited/overridden</li>
* </ul>
* </p>
*
* <p>
* Setting output value/quality/timestamp of real datapoint is not supported!
* </p>
*
* @author Yoga
*
*/
public class RealDataPoint extends DataPoint
{
// expression for engineering conversion
// for linear conversion: y = mx + b
protected ExpressionParser ecParser = null;
/**
* Default ctor.
*
* @param inType
* - the datapoint type
* @param inDataType
* - the internal data type
*/
public RealDataPoint(CosDpValueTypeEnum inDataType)
{
super(inDataType);
// override the logger
logger = LoggerManager.getLogger(this.getClass().getName());
// set the default quality to bad.
// this way, as soon as the source quality is good, the quality is updated.
// this in turn will update the datapoint timestamp
sourceQuality = CosDpQualityEnum.QualityBad;
outputQuality = CosDpQualityEnum.QualityBad;
}
@Override
public EDataPointType getType()
{
return EDataPointType.TYPE_REAL;
}
private CosDpValueUnion perform_eng_conversion(CosDpValueUnion value)
{
//TODO: check if ecParser is configured!
switch (value.discriminator().value())
{
case CosDpValueTypeEnum._TypeBoolean:
value.boolValue(perform_eng_conversion(value.boolValue()));
break;
case CosDpValueTypeEnum._TypeDouble:
value.dblValue(perform_eng_conversion(value.dblValue()));
break;
case CosDpValueTypeEnum._TypeNumber:
value.longValue(perform_eng_conversion(value.longValue()));
break;
case CosDpValueTypeEnum._TypeUnsigned:
value.unsignedValue(perform_eng_conversion(value
.unsignedValue()));
break;
case CosDpValueTypeEnum._TypeString:
value.charValue(perform_eng_conversion(value.charValue()));
break;
}
return value;
}
private boolean perform_eng_conversion(boolean value)
{
// update the input value parameter
Boolean objVal = Boolean.valueOf(value);
ecParser.set(0, objVal);
// perform the conversion
try
{
objVal = (Boolean) ecParser.evaluate();
return objVal.booleanValue();
}
catch (Exception ex)
{
logger.warn("Can not evaluate engineering conversion. Exception: "
+ ex.toString());
}
// return the original value
return value;
}
private int perform_eng_conversion(int value)
{
// update the input value parameter
Integer objVal = Integer.valueOf(value);
ecParser.set(0, objVal);
// perform the conversion
try
{
objVal = (Integer) ecParser.evaluate();
return objVal.intValue();
}
catch (Exception ex)
{
logger.warn("Can not evaluate engineering conversion. Exception: "
+ ex.toString());
}
// return the original value
return value;
}
private double perform_eng_conversion(double value)
{
// update the input value parameter
Double objVal = new Double(value);
ecParser.set(0, objVal);
// perform the conversion
try
{
objVal = (Double) ecParser.evaluate();
return objVal.doubleValue();
}
catch (Exception ex)
{
logger.warn("Can not evaluate engineering conversion. Exception: "
+ ex.toString());
}
// return the original value
return value;
}
private String perform_eng_conversion(String value)
{
// update the input value parameter
ecParser.set(0, value);
// perform the conversion
try
{
value = (String) ecParser.evaluate();
}
catch (Exception ex)
{
logger.warn("Can not evaluate engineering conversion. Exception: "
+ ex.toString());
}
// return the original value
return value;
}
private boolean isWithinDeadBand(CosDpValueUnion inValue)
{
switch (sourceValue.discriminator().value())
{
case CosDpValueTypeEnum._TypeNumber:
{
int curValue = sourceValue.longValue();
int newValue = inValue.longValue();
if (newValue < (curValue - deadband)
|| newValue > (curValue + deadband))
{
return false;
}
else
{
return true;
}
}
case CosDpValueTypeEnum._TypeUnsigned:
{
int curValue = sourceValue.unsignedValue();
int newValue = inValue.unsignedValue();
if (newValue < (curValue - deadband)
|| newValue > (curValue + deadband))
{
return false;
}
else
{
return true;
}
}
case CosDpValueTypeEnum._TypeDouble:
double curValue = sourceValue.dblValue();
double newValue = inValue.dblValue();
if (newValue < (curValue - deadband)
|| newValue > (curValue + deadband))
{
return false;
}
else
{
return true;
}
}
// default: not supported
return false;
}
@Override
public synchronized int setSourceQuality(CosDpQualityEnum inQuality,
boolean inUpdateTimestamp)
{
// validation
if (inQuality == null)
{
return -1;
}
// copy the quality
int status = 0;
if (sourceQuality != inQuality)
{
sourceQuality = inQuality;
status = +1;
}
// only update timestamp if quality has changed
if (status > 0)
{
// update the source value timestamp if necessary
if (inUpdateTimestamp)
{
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
}
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify dpserver
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
}
return status;
}
@Override
public synchronized int setSourceTimestamp(long inTimestamp)
{
// validation: inTimestamp
// none
// copy the quality
int status = 0;
if (sourceTimestamp != inTimestamp)
{
sourceTimestamp = inTimestamp;
status = +1;
}
// only synchronize if timestamp has changed
if (status > 0)
{
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify dpserver
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
}
return status;
}
@Override
public synchronized int setSourceValue(CosDpValueUnion inValue,
boolean inUpdateTimestamp)
{
// validation
if (inValue == null)
{
return -1;
}
// validation: inUpdateTimestamp
// none
CosDpValueUnion value = createCosDpValueUnion(this
.getInternalDataType());
// perform value translation first if necessary. This simplifies the
// next calculations.
if (copy(value, inValue) < 0)
{
return -1;
}
// if there is engineering conversion, do it here
if (ecParser != null && ecParser.isValid())
{
value = perform_eng_conversion(value);
}
// check for deadband
if (isWithinDeadBand(value))
{
return 0;
}
// copy the value
int status = copy(sourceValue, value);
if (status > 0)
{
// update the source value timestamp. if necessary we can override
// it later
if (inUpdateTimestamp)
{
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
}
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify dpserver
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
}
return status;
}
@Override
public synchronized int setBooleanSourceValue(boolean inValue,
boolean inUpdateTimestamp)
{
// validation: inValue
// none
// validation: inUpdateTimestamp
// none
// we can do a value translation here. but I think it is not worth the
// effort. instead, just return with error with the value type is not
// boolean
if (sourceValue.discriminator() != CosDpValueTypeEnum.TypeBoolean)
{
return -1;
}
boolean value = false;
// if there is engineering conversion, do it here
if (ecParser != null && ecParser.isValid())
{
value = perform_eng_conversion(inValue);
}
else
{
value = inValue;
}
// check for deadband not supported for boolean data type
// copy the value
int status = setBooleanValue(sourceValue, value);
if (status > 0)
{
// update the source value timestamp. if necessary we can override
// it later
if (inUpdateTimestamp)
{
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
}
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify dpserver
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
}
return status;
}
@Override
public synchronized int setNumberSourceValue(int inValue,
boolean inUpdateTimestamp)
{
// validation: inValue
// none
// validation: inUpdateTimestamp
// none
// we can do a value translation here. but I think it is not worth the
// effort. instead, just return with error with the value type is not
// boolean
if (sourceValue.discriminator() != CosDpValueTypeEnum.TypeNumber)
{
return -1;
}
int value = 0;
// if there is engineering conversion, do it here
if (ecParser != null && ecParser.isValid())
{
value = perform_eng_conversion(inValue);
}
else
{
value = inValue;
}
// check for deadband
int curValue = sourceValue.longValue();
if (value >= (curValue - deadband) && value <= (curValue + deadband))
{
return 0;
}
// copy the value
int status = setNumberValue(sourceValue, value);
if (status > 0)
{
// update the source value timestamp. if necessary we can override
// it later
if (inUpdateTimestamp)
{
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
}
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify dpserver
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
}
return status;
}
@Override
public synchronized int setUnsignedSourceValue(int inValue,
boolean inUpdateTimestamp)
{
// validation: inValue
// none
// validation: inUpdateTimestamp
// none
// we can do a value translation here. but I think it is not worth the
// effort. instead, just return with error with the value type is not
// boolean
if (sourceValue.discriminator() != CosDpValueTypeEnum.TypeUnsigned)
{
return -1;
}
int value = 0;
// if there is engineering conversion, do it here
if (ecParser != null && ecParser.isValid())
{
value = perform_eng_conversion(inValue);
}
else
{
value = inValue;
}
// check for deadband
int curValue = sourceValue.unsignedValue();
if (value >= (curValue - deadband) && value <= (curValue + deadband))
{
return 0;
}
// copy the value
int status = setUnsignedValue(sourceValue, value);
if (status > 0)
{
// update the source value timestamp. if necessary we can override
// it later
if (inUpdateTimestamp)
{
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
}
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify datastore
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
}
return status;
}
@Override
public synchronized int setDoubleSourceValue(double inValue,
boolean inUpdateTimestamp)
{
// validation: inValue
// none
// validation: inUpdateTimestamp
// none
// we can do a value translation here. but I think it is not worth the
// effort. instead, just return with error with the value type is not
// boolean
if (sourceValue.discriminator() != CosDpValueTypeEnum.TypeDouble)
{
return -1;
}
double value = 0;
// if there is engineering conversion, do it here
if (ecParser != null && ecParser.isValid())
{
value = perform_eng_conversion(inValue);
}
else
{
value = inValue;
}
// check for deadband
double curValue = sourceValue.dblValue();
if (value >= (curValue - deadband) && value <= (curValue + deadband))
{
return 0;
}
// copy the value
int status = setDoubleValue(sourceValue, value);
if (status > 0)
{
// update the source value timestamp. if necessary we can override
// it later
if (inUpdateTimestamp)
{
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
}
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify datastore
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
}
return status;
}
@Override
public synchronized int setStringSourceValue(String inValue,
boolean inUpdateTimestamp)
{
// validation: inValue
if (inValue == null)
{
return -1;
}
// validation: inUpdateTimestamp
// none
// we can do a value translation here. but I think it is not worth the
// effort. instead, just return with error with the value type is not
// boolean
if (sourceValue.discriminator() != CosDpValueTypeEnum.TypeString)
{
return -1;
}
String value = "";
// if there is engineering conversion, do it here
if (ecParser != null && ecParser.isValid())
{
value = perform_eng_conversion(inValue);
}
else
{
value = inValue;
}
// check for deadband is not supported for string type
// copy the value
int status = setStringValue(sourceValue, value);
if (status > 0)
{
// update the source value timestamp. if necessary we can override
// it later
if (inUpdateTimestamp)
{
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
}
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify datastore
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
}
return status;
}
@Override
public synchronized int setSourceValue(CosDpValueStruct inValue)
{
// validation: inValue
if (inValue == null)
{
return -1;
}
CosDpValueUnion value = createCosDpValueUnion(this
.getInternalDataType());
int status = 0;
// perform value translation first if necessary. This simplifies the
// next calculations.
if (copy(value, inValue.value) < 0)
{
return -1;
}
// if there is engineering conversion, do it here
if (ecParser != null && ecParser.isValid())
{
value = perform_eng_conversion(value);
}
// check for deadband
int status1 = 0;
if (!isWithinDeadBand(value))
{
// not within deadband. copy the value
status1 = copy(sourceValue, value);
status1 = +1;
}
// copy the quality
int status2 = 0;
if (sourceQuality != inValue.quality)
{
sourceQuality = inValue.quality;
status2 = +1;
}
// overall status
if (status1 > 0 || status2 > 0)
{
status = +1;
}
else if (status1 == 0 && status2 == 0)
{
status = 0;
}
else
{
status = -1;
}
// only update timestamp if either value or quality has changed
if (status > 0)
{
status = 1;
// update the source value timestamp
sourceTimestamp = inValue.timestamp;
// update the update timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// synchronize source value and output value
if (_synchronize() > 0)
{
// re-evaluate all related calculated points, if any
for (int i = 0; i < calculatedPoints.size(); i++)
{
calculatedPoints.get(i).evaluate();
}
// re-calculate all related meter points, if any
for (int i = 0; i < meterPoints.size(); i++)
{
meterPoints.get(i).calculateMeterValue();
}
// re-evaluate all child points' quality, if any
for (int i = 0; i < childPoints.size(); i++)
{
childPoints.get(i).updateQuality();
}
}
// notify dpserver
if (datastore != null)
{
// TODO: implement
// datastore.onDataPointSourceChange(this);
}
}
return status;
}
@Override
public synchronized boolean setEngineeringConversion(String inExpression)
{
// validation: inExpression
if (inExpression == null || inExpression.isEmpty())
{
return false;
}
// only supported for number/unsigned/double (at least for the moment)
int dataType = getInternalDataType().value();
if (dataType == CosDpValueTypeEnum._TypeBoolean
|| dataType == CosDpValueTypeEnum._TypeString)
{
return false;
}
// first of all get the return type. this will also be the input value's
// type
Class<?> type = boolean.class;
Object initInputValue = null;
switch (sourceValue.discriminator().value())
{
case CosDpValueTypeEnum._TypeNumber:
type = int.class;
initInputValue = Integer.valueOf(0);
break;
case CosDpValueTypeEnum._TypeUnsigned:
type = int.class;
initInputValue = Integer.valueOf(0);
break;
case CosDpValueTypeEnum._TypeBoolean:
type = boolean.class;
initInputValue = Boolean.valueOf(false);
break;
case CosDpValueTypeEnum._TypeDouble:
type = double.class;
initInputValue = new Double(0.0);
break;
case CosDpValueTypeEnum._TypeString:
type = String.class;
initInputValue = "";
break;
}
// protect against "X" or "x"
inExpression = inExpression.toLowerCase();
// create the parser
ecParser = new ExpressionParser(inExpression, type);
// create the input variable
ecParser.createVariable("x", type, initInputValue);
// try to evaluate
try
{
Object result = ecParser.evaluate();
if (result == null)
{
return false;
}
}
catch (Exception ex)
{
logger.error("Can not set engineering conversion expression: "
+ ex.getMessage());
return false;
}
// successful
return true;
}
@Override
public synchronized double setDeadBandValue(double inDeadBand)
{
// validation: inDeadBand
// none
// only supported for number/unsigned/double (at least for the moment)
int dataType = getInternalDataType().value();
if (dataType == CosDpValueTypeEnum._TypeBoolean
|| dataType == CosDpValueTypeEnum._TypeString)
{
return 0;
}
// if it is number/unsigned, round it down
if (dataType == CosDpValueTypeEnum._TypeNumber
|| dataType == CosDpValueTypeEnum._TypeUnsigned)
{
inDeadBand = Math.floor(inDeadBand);
}
// set the value
deadband = inDeadBand;
return deadband;
}
@Override
public synchronized boolean setInhibit()
{
// pass the inhibit to underlying subsystem if necessary
if (subsystem != null && subsystem.isSourceInhibit())
{
return subsystem.setInhibit(this);
}
// otherwise, it is a local inhibit. pass it to base class
return super.setInhibit();
}
@Override
public synchronized boolean removeInhibit()
{
// remove the inhibit in the underlying subsystem if necessary
if (subsystem != null && subsystem.isSourceInhibit())
{
return subsystem.removeInhibit(this);
}
// otherwise, it is a local inhibit. pass it to base class
return super.removeInhibit();
}
@Override
public synchronized boolean setOverride(CosDpValueUnion inOverrideValue)
{
// validation: invalid value
if (inOverrideValue == null)
{
return false;
}
// pass the override to underlying subsystem if necessary
if (subsystem != null && subsystem.isSourceOverride())
{
return subsystem.setOverride(this, inOverrideValue);
}
// otherwise, it is a local override. pass it to base class
return super.setOverride(inOverrideValue);
}
@Override
public synchronized boolean removeOverride()
{
// remove the inhibit in the underlying subsystem if necessary
if (subsystem != null && subsystem.isSourceOverride())
{
return subsystem.removeOverride(this);
}
// otherwise, it is a local override. pass it to base class
return super.removeOverride();
}
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized int setValue(CosDpValueUnion inValue) {
// //default implementation
// return -1;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized int setBooleanValue(boolean inValue) {
// // default implementation
// return -1;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized int setNumberValue(int inValue) {
// // default implementation
// return -1;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized int setUnsignedValue(int inValue) {
// // default implementation
// return -1;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized int setDoubleValue(double inValue) {
// // default implementation
// return -1;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized int setStringValue(String inValue) {
// // default implementation
// return -1;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized boolean setReturnCondition(String inExpression,
// DataPointList inDpList) {
// //default implementation
// return false;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized boolean checkReturnCondition() {
// //default implementation
// return false;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized boolean setLaunchCondition(String inExpression,
// DataPointList inDpList) {
// //default implementation
// return false;
// }
//
// /**
// * Not applicable to real datapoint.
// */
// @Override
// public synchronized boolean checkLaunchCondition() {
// //default implementation
// return false;
// }
}