package tcg.scada.da;
import java.util.ArrayList;
import java.util.Calendar;
import tcg.common.LoggerManager;
import tcg.common.util.ExpressionParser;
import tcg.scada.cos.CosDpQualityEnum;
import tcg.scada.cos.CosDpValueTypeEnum;
import tcg.scada.cos.CosDpValueUnion;
/**
* Implementation of calculated datapoint
*
* <p>
* Expected behaviour for calculated datapoint:
* <ul>
* <li>Source value: expression result</li>
* <li>Source quality: GoodQuality if expression is evaluated successfully</li>
* <li>Source timestamp: timestamp of last expression evaluation</li>
* <li>Output value: expression result if not inhibited/overridden</li>
* <li>Output quality: internal calculation</li>
* <li>Output timestamp: timestamp of last expression evaluation, if not
* inhibited/overridden</li>
* </ul>
* </p>
*
* <p>
* Setting output value/quality/timestamp is not supported for calculated
* datapoint.
* </p>
*
* <p>
* Setting source value/quality/timestamp is also not supported for calculated
* datapoint. To update the value, run evaluate(). This will update the source
* value to the expression result, the source quality to the expression quality
* and the source timestamp to current time.
* </p>
*
* <p>
* <b>On the other hand, inhibit and override IS supported for calculated
* datapoint.</b> Alarm is also supported.
* </p>
*
* <p>
* Deadband and engineering conversion is not supported for virtual datapoint.
* </p>
*
* @author Yoga
*
*/
public class CalculatedDataPoint extends DataPoint
{
// expression value for calculated datapoint
protected String expression = "";
protected ExpressionParser expressionParser = null;
protected ArrayList<IDataPoint> expressionParams = null;
// flag to prevent infinite recursive calculation
// private final Semaphore semaphore_ = new Semaphore(1, true);
/**
* Default ctor.
*
* @param inType
* - the datapoint type
* @param inDataType
* - the internal data type
*/
public CalculatedDataPoint(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_CALCULATED;
}
@Override
public synchronized boolean setExpression(String inExpression,
DataPointList inDpList)
{
// validation: inExpression
if (inExpression == null || inExpression.isEmpty())
{
return false;
}
// validation: inDpList
// none
// get the expression type
// first of all get the return type
Class<?> type = int.class; // default is int
switch (outputValue.discriminator().value())
{
case CosDpValueTypeEnum._TypeBoolean:
type = boolean.class;
break;
case CosDpValueTypeEnum._TypeNumber:
type = int.class;
break;
case CosDpValueTypeEnum._TypeUnsigned:
type = int.class;
break;
case CosDpValueTypeEnum._TypeDouble:
type = double.class;
break;
case CosDpValueTypeEnum._TypeString:
type = String.class;
break;
}
// parse the expression. this must be done after all datapoints have
// been created
// otherwise, invalid/non-existent datapoints will be just marked as 0.
ArrayList<IDataPoint> params = new ArrayList<IDataPoint>();
ArrayList<String> varnames = new ArrayList<String>();
ArrayList<Class<?>> vartypes = new ArrayList<Class<?>>();
ArrayList<Object> varvalues = new ArrayList<Object>();
// parse the expression
String parsedExpression = _parseExpression(inDpList, inExpression,
params, varnames, vartypes, varvalues);
// create the parser
expressionParser = new ExpressionParser(parsedExpression, type);
// create all the variables
for (int i = 0; i < varnames.size(); i++)
{
expressionParser.createVariable(varnames.get(i), vartypes.get(i),
varvalues.get(i));
}
// copy the list of datapoint component
expressionParams = params;
// try to evaluate
boolean status = true;
try
{
Object result = expressionParser.evaluate();
if (result == null)
{
status = false;
}
}
catch (Exception ex)
{
logger
.error("Can not set datapoint expression: "
+ ex.getMessage());
status = false;
}
// if not valid, set the source quality to bad and just quit
if (!status)
{
sourceQuality = CosDpQualityEnum.QualityBad;
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
// synchronize
_synchronize();
// update the timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
// return
return false;
}
// for each datapoint param, add this datapoint as child point
IDataPoint datapoint = null;
for (int i = 0; i < expressionParams.size(); i++)
{
datapoint = expressionParams.get(i);
if (datapoint != null)
{
datapoint.addCalculatedPoint(this);
}
}
// update source quality and timestamp
sourceQuality = CosDpQualityEnum.QualityGood;
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
// synchronize
_synchronize();
// update the timestamp
updateTimestamp_ = Calendar.getInstance().getTimeInMillis();
return true;
}
@Override
public synchronized int evaluate()
{
// validation: expression is never set
if (expressionParser == null)
{
return -1;
}
// check for invalid expression
if (!expressionParser.isValid())
{
// it has been parsed and found invalid. prevent re-parsing
logger
.error("Invalid expression. Check the first time this happens for error.");
return -1;
}
//TODO: prevent for infinite recursive relationship
int retval = 0;
IDataPoint datapoint = null;
Object value = null;
CosDpQualityEnum expressionQuality = CosDpQualityEnum.QualityGood;
CosDpValueUnion expressionValue = null;
// update the value of expression parameters
for (int i = 0; i < expressionParams.size(); i++)
{
datapoint = expressionParams.get(i);
if (datapoint == null)
{
logger.trace("DP parameter : UNKNOWN");
// this, in theory, should never happens
expressionQuality = CosDpQualityEnum.QualityBad;
// default value
value = Integer.valueOf(0);
}
else
{
logger.trace("DP parameter : " + datapoint.getName());
// check for bad quality, in which case the expression quality
// will also be set to bad
// Note: Anything that is not QualityGood is considered bad quality.
// This includes QualityInhibit and QualityOverride
if (datapoint.getQuality() != CosDpQualityEnum.QualityGood)
{
expressionQuality = CosDpQualityEnum.QualityBad;
}
// calculate the parameter value
value = convCosDpValueUnion2Object(datapoint.getValue());
}
// set the parameter with the new value
expressionParser.set(i, value);
// verbose
logger.trace("DP parameter value : " + value.toString());
}
// evaluate the expression value
try
{
value = expressionParser.evaluate();
if (value == null)
{
logger.warn("Can not evaluate expression. Null result");
expressionQuality = CosDpQualityEnum.QualityBad;
}
}
catch (Exception ex)
{
logger.warn("Can not evaluate expression. Exception: "
+ ex.toString());
expressionQuality = CosDpQualityEnum.QualityBad;
}
// convert it to CosDpValueUnion
expressionValue = convObject2CosDpValueUnion(sourceValue
.discriminator(), value);
// update the source value with the expression value
int status1 = copy(sourceValue, expressionValue);
;
// update the source quality with the expression quality
int status2 = 0;
if (sourceQuality != expressionQuality)
{
sourceQuality = expressionQuality;
status2 = +1;
}
// return value
if (status1 > 0 || status2 > 0)
{
retval = +1;
}
else if (status1 == 0 || status2 == 0)
{
retval = 0; // no updates or changes
}
else
{
retval = -1; // failure
}
// post-processing
if (retval == 1)
{
// update the source timestamp
sourceTimestamp = Calendar.getInstance().getTimeInMillis();
// update the update timestamp
updateTimestamp_ = sourceTimestamp;
// synchronize the source and output value/quality/timestamp
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();
}
}
// TODO: notify datastore
if (datastore != null)
{
// dpServer.onDataPointChange(this);
}
} // if (retval == 1)
return retval;
}
}