package tcg.scada.da;
import java.util.Calendar;
import tcg.common.LoggerManager;
import tcg.scada.cos.CosDpQualityEnum;
import tcg.scada.cos.CosDpValueTypeEnum;
/**
* Implementation of metering datapoint.
*
* <p>
* Expected behaviour for metering datapoint:
* <ul>
* <li>Source value: accumulated meter value</li>
* <li>Source quality: quality of source datapoint</li>
* <li>Source timestamp: timestamp of source datapoint</li>
* <li>Output value: accumulated meter value</li>
* <li>Output quality: internal calculation</li>
* <li>Output timestamp: timestamp of source datapoint or last manual adjustment
* </li>
* </ul>
* </p>
*
* <p>
* Setting output value/quality/timestamp is not supported for metering
* datapoint.
* </p>
*
* <p>
* Setting source value/quality/timestamp is also not supported for metering
* datapoint. To update the value, run calculateMeterValue(). This will update
* the source value to the accumulated result, the source quality to the source
* datapoint quality and the source timestamp to source datapoint's timestamp.
* </p>
*
* <p>
* <b>On the other hand, inhibit and override IS supported for metering
* datapoint.</b> Alarm is also supported.
* </p>
*
* <p>
* Deadband and engineering conversion is not supported for virtual datapoint.
* </p>
*
* <p>
* Deadband and engineering conversion is not supported for metering datapoint.
* </p>
*
* @author Yoga
*
*/
public class MeterDataPoint extends DataPoint
{
// metering point
protected int readingRollOver = Integer.MAX_VALUE; // 2147483647 (2^31 - 1)
protected int datapointRollOver = Integer.MAX_VALUE;
protected int lastReadingValue = 0;
// source point where the reading value is taken from
protected IDataPoint sourcePoint = null;
/**
* Default ctor.
*
* @param inDataType
* - the internal data type
*/
public MeterDataPoint(CosDpValueTypeEnum inDataType)
{
// metering point is only supported for number/unsigned data type
if (inDataType.value() != CosDpValueTypeEnum._TypeNumber
&& inDataType.value() != CosDpValueTypeEnum._TypeUnsigned)
{
// force it to be number type
inDataType = CosDpValueTypeEnum.TypeNumber;
}
super._setDataType(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_METER;
}
/**
* Helper function to initialize the last meter reading. This will set the
* source value without triggering notification to datastore.
*
* @param inLastReading
*/
protected synchronized void _setLastMeterReading(int inLastReading)
{
lastReadingValue = inLastReading;
}
/**
* Get last meter reading
*
* @param the
* last meter reading
*/
protected synchronized int _getLastMeterReading()
{
return lastReadingValue;
}
@Override
public synchronized int adjustMeterValue(int inAdjustment)
{
// validation
if (inAdjustment == 0)
{
return 0;
}
// get current value
long curMeterValue = 0;
if (sourceValue.discriminator() == CosDpValueTypeEnum.TypeNumber)
{
curMeterValue = sourceValue.longValue();
}
else
{
curMeterValue = sourceValue.unsignedValue();
}
// add the adjustment
curMeterValue += inAdjustment;
// check for datapoint rollover
if (curMeterValue >= datapointRollOver)
{
curMeterValue -= datapointRollOver;
}
else if (curMeterValue < 0)
{
inAdjustment += curMeterValue;
curMeterValue = 0;
}
// DO NOT ADJUST THE LAST_METER_READING!!!
// set the datapoint value
if (sourceValue.discriminator() == CosDpValueTypeEnum.TypeNumber)
{
sourceValue.longValue((int) curMeterValue);
}
else
{
sourceValue.unsignedValue((int) curMeterValue);
}
// 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);
}
// return value
return inAdjustment;
}
@Override
public int setReadingRollOver(int inRollOver)
{
// validation: inRollOver
if (inRollOver <= 0)
{
return 0;
}
// set local variable
readingRollOver = inRollOver;
return readingRollOver;
}
@Override
public int getReadingRollOver()
{
return readingRollOver;
}
@Override
public int setDataPointRollOver(int inRollOver)
{
// validation: inRollOver
if (inRollOver <= 0)
{
return 0;
}
// set local variable
datapointRollOver = inRollOver;
return datapointRollOver;
}
@Override
public int getDataPointRollOver()
{
return datapointRollOver;
}
@Override
public boolean setMeterSourcePoint(IDataPoint inDataPoint)
{
// validation: inDataPoint
if (inDataPoint == null)
{
return false;
}
// only number/unsigned as allowed as source point
CosDpValueTypeEnum dataType = inDataPoint.getInternalDataType();
if (dataType != CosDpValueTypeEnum.TypeNumber
&& dataType != CosDpValueTypeEnum.TypeUnsigned)
{
return false;
}
// set as source point. also add this datapoint for notification in
// source datapoint
sourcePoint = inDataPoint;
sourcePoint.addMeterPoint(this);
// calculate the meter value right away
calculateMeterValue();
return true;
}
@Override
public int calculateMeterValue()
{
// validation: source point is never set
if (sourcePoint == null)
{
return -1;
}
// get current source value
int curValue = 0;
if (sourcePoint.getInternalDataType() == CosDpValueTypeEnum.TypeNumber)
{
curValue = sourcePoint.getValue().longValue();
}
else
{
curValue = sourcePoint.getValue().unsignedValue();
}
// calculate the accumulated value
int status1 = 0;
if (curValue != lastReadingValue)
{
// if curValue is the same as last reading value, ignore calculating
// the value
long meterReading = 0;
// check for reading rollover
if (curValue < lastReadingValue)
{
meterReading = curValue + readingRollOver - lastReadingValue;
logger
.warn("Equipment reading has rolled over. Current reading: "
+ curValue
+ ". Last reading: "
+ lastReadingValue
+ ". Equipment roll-over value: "
+ datapointRollOver);
}
else
{
meterReading = curValue - lastReadingValue;
}
// get current meter value
long curMeterValue = 0;
if (sourceValue.discriminator() == CosDpValueTypeEnum.TypeNumber)
{
curMeterValue = sourceValue.longValue();
}
else
{
curMeterValue = sourceValue.unsignedValue();
}
// TODO: check for datapoint rollover
// add the new reading value
curMeterValue += meterReading;
// rollover if necessary
if (curMeterValue >= datapointRollOver)
{
// datapoint rollover
curMeterValue -= datapointRollOver;
logger.warn("Metering point has rolled over. Current value: "
+ curMeterValue + ". Datapoint roll-over value: "
+ datapointRollOver);
}
// set the datapoint value
if (sourceValue.discriminator() == CosDpValueTypeEnum.TypeNumber)
{
sourceValue.longValue((int) curMeterValue);
}
else
{
sourceValue.unsignedValue((int) curMeterValue);
}
// update the last reading value
lastReadingValue = curValue;
// TODO: notify the datastore
status1 = +1;
}
// set the quality
int status2 = 0;
if (sourceQuality != sourcePoint.getQuality())
{
sourceQuality = sourcePoint.getQuality();
status2 = +1;
// TODO: notify the datastore
}
// overall status
int status = 0;
if (status1 > 0 || status2 > 0)
{
status = +1;
}
else
{
status = 0;
}
// post-processing
if (status > 0)
{
// 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);
}
}
return status;
}
}