package org.yamcs.simulation;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamcs.ConfigurationException;
import org.yamcs.parameter.ParameterValue;
import org.yamcs.protobuf.Pvalue.AcquisitionStatus;
import org.yamcs.protobuf.Pvalue.MonitoringResult;
import org.yamcs.protobuf.Pvalue.RangeCondition;
import org.yamcs.simulation.generated.PpSimulation;
import org.yamcs.simulation.generated.PpSimulation.ParameterSequence;
import org.yamcs.tctm.ParameterDataLink;
import org.yamcs.tctm.ParameterSink;
import org.yamcs.utils.TimeEncoding;
import org.yamcs.xtce.FloatParameterType;
import org.yamcs.xtce.Parameter;
import org.yamcs.xtce.ParameterType;
import org.yamcs.xtce.XtceDb;
import org.yamcs.xtceproc.XtceDbFactory;
import com.google.common.util.concurrent.AbstractExecutionThreadService;
// Command line to generate xml classes:
//[...]/yamcs/yamcs-simulation/src/main/resources/org/yamcs/xsd$ xjc simulation_data.xsd -p org.yamcs.simulation.generated -d [...]/yamcs/yamcs-simulation/src/main/java/
public class SimulationPpProvider extends AbstractExecutionThreadService implements ParameterDataLink, Runnable {
protected volatile long datacount = 0;
private ParameterSink ppListener;
protected volatile boolean disabled = false;
PpSimulation simulationData = null;
// static String SIMULATION_DATA =
// "/home/msc/development/git/yamcs/live/etc/simulation.xml";
static String simulationDataPath = "";
private Logger log = LoggerFactory.getLogger(this.getClass().getName());
XtceDb xtceDb;
Random rand = new Random();
public SimulationPpProvider(String yamcsInstance, String name, LinkedHashMap<String,String> args) throws ConfigurationException {
xtceDb = XtceDbFactory.getInstance(yamcsInstance);
setSimulationData((String) args.get("simulationDataPath"));
simulationData = loadSimulationData(simulationDataPath);
}
public SimulationPpProvider() {
}
public void setSimulationData(String xmlFilePath) {
simulationDataPath = xmlFilePath;
simulationData = loadSimulationData(simulationDataPath);
}
@Override
public String getLinkStatus() {
if (disabled) {
return "DISABLED";
} else {
return "OK";
}
}
@Override
public String getDetailedStatus() {
return getLinkStatus();
}
@Override
public void enable() {
// reload simulation data and reset simulation parameters
if (disabled) {
simulationData = loadSimulationData(simulationDataPath);
}
disabled = false;
}
@Override
public void disable() {
disabled = true;
}
@Override
public boolean isDisabled() {
return disabled;
}
@Override
public long getDataCount() {
return datacount;
}
@Override
public void setParameterSink(ParameterSink ppListener) {
this.ppListener = ppListener;
}
// ///
// run()
// Entry point to run the simulation
//
@Override
public void run() {
while (IsRunning()) {
try {
if (!disabled) {
// run simulation
processSimulationData();
} else {
Thread.sleep(1000);
}
} catch (Exception e) {
log.warn("exception thrown when processing a parameter. Details:\n"
+ e.toString());
e.printStackTrace();
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
}
}
}
}
boolean IsRunning() {
return isRunning();
}
// ////
// ProcessSimulationData()
// Process the speceificed simulation scenario
//
public Date simulationStartTime;
public Date simulationRealStartTime;
public int simulationStepLengthMs;
public long simutationStep = 0;
public boolean loopSimulation = false;
public void processSimulationData() {
// get simulation starting date
if (simulationData.getStartDate() != null)
simulationStartTime = simulationData.getStartDate()
.toGregorianCalendar().getTime();
else
simulationStartTime = new Date();
simulationRealStartTime = new Date();
simutationStep = 0;
// get length of a simulation step
simulationStepLengthMs = simulationData.getStepLengthMs();
// get loop status
loopSimulation = simulationData.isLoop() != null
&& simulationData.isLoop();
// process each sequence
do {
List<PpSimulation.ParameterSequence> pss = simulationData
.getParameterSequence();
for (ParameterSequence ps : pss) {
processParameterSequence(ps);
}
if (!IsRunning() || disabled) {
break;
}
} while (loopSimulation);
}
// ////
// ProcessParameterSequence()
// Process a sequence of the simulation scenario
//
private void processParameterSequence(ParameterSequence ps) {
int repeatCount = 0;
int maxRepeat = ps.getRepeat() != null ? ps.getRepeat() : 1;
boolean loopSequence = ps.isLoop() != null && ps.isLoop();
// repeat the sequence as specified
while (loopSequence || repeatCount++ < maxRepeat) {
if (!IsRunning() || disabled)
break;
// process step offset
int stepOffset = ps.getStepOffset() == null ? 0 : ps
.getStepOffset();
processVoidStep(stepOffset);
simutationStep += stepOffset;
// initialize step count for this sequence
List<ParameterSequence.Parameter> parameters = ps.getParameter();
if(parameters.size() == 0)
return;
int lastSequenceStep = parameters.get(parameters.size() - 1)
.getAquisitionStep();
int currentParameterIndex = 0;
// process each step of the sequence
for (int sequenceStep = 0; sequenceStep <= lastSequenceStep; sequenceStep++) {
if (!IsRunning() || disabled)
break;
ParameterSequence.Parameter currentParameter = parameters
.get(currentParameterIndex);
// case where there is no parameter to send at this step
if (currentParameter.getAquisitionStep() != sequenceStep) {
assert (currentParameter.getAquisitionStep() > sequenceStep);
processVoidStep(1);
} else {
// there is at least 1 parameter to send at this step
List<ParameterSequence.Parameter> stepParameters = new ArrayList<ParameterSequence.Parameter>();
while (currentParameter.getAquisitionStep() == sequenceStep) {
stepParameters.add(currentParameter);
currentParameterIndex++;
// add next parameter if available
if (currentParameterIndex < parameters.size())
currentParameter = parameters
.get(currentParameterIndex);
else
break;
}
processParameters(stepParameters);
stepParameters.clear();
}
simutationStep++;
}
}
}
// ///
// ProcessParameters()
// Create a specified parameter and insert it in the Yamcs PP Listener
//
private void processParameters(List<ParameterSequence.Parameter> stepParameters) {
String groupName = "simulation";
List<ParameterValue> pvs = new LinkedList<ParameterValue>();
for (ParameterSequence.Parameter sParameter : stepParameters) {
if (!IsRunning() || disabled)
break;
// compute value
float value;
if (sParameter.getValueType().equals("random"))
value = rand.nextFloat();
else
value = sParameter.getValue().floatValue();
// create generationTime and acquisitionTime
long acquisitionTime = simulationStartTime.getTime()
+ simutationStep * simulationStepLengthMs;
long generationTime = acquisitionTime
- (sParameter.getAquisitionStep() - sParameter
.getGenerationStep()) * simulationStepLengthMs;
// convert time to 'instant'
acquisitionTime = TimeEncoding.fromUnixTime(acquisitionTime);
generationTime = TimeEncoding.fromUnixTime(generationTime);
// get monitoring result
String monitoringResult = sParameter.getMonitoringResult();
ParameterValue pv = createPv(sParameter.getSpaceSystem(),
sParameter.getParaName(), generationTime, acquisitionTime,
value, monitoringResult);
if (pv != null)
pvs.add(pv);
}
datacount += stepParameters.size();
ppListener.updateParameters(new Date().getTime(), groupName, (int) datacount, pvs);
Long nextStepDate = simulationRealStartTime.getTime() + simulationStepLengthMs * simutationStep;
Long delayBeforeNextStep = nextStepDate - new Date().getTime();
try {
if(delayBeforeNextStep > 0)
Thread.sleep(delayBeforeNextStep);
} catch (Exception e) {
log.error("", e);
}
}
// //
// ProcessVoidStep()
// Used when no parameters need to be inserted at a given step of the
// simulation scenario
private void processVoidStep(int nbSteps) {
try {
log.trace("Processing " + nbSteps + " void steps");
Thread.sleep(simulationStepLengthMs * nbSteps);
} catch (InterruptedException e) {
log.error(e.toString());
}
}
// ///
// LoadSimulationData()
// Load data from an XML file
//
public PpSimulation loadSimulationData(String fileName) {
try {
final JAXBContext jc = JAXBContext.newInstance(PpSimulation.class);
final Unmarshaller unmarshaller = jc.createUnmarshaller();
final PpSimulation ppSimulation = (PpSimulation) unmarshaller
.unmarshal(new FileReader(fileName));
return ppSimulation;
} catch (Exception e) {
log.error("Unable to load Simulation Data. Check the XML file is correct. Details:\n"
+ e.toString());
throw new ConfigurationException("Unable to load Simulation Data. Check the XML file is correct. Details:\n"+ e.toString());
}
}
// ///
// CreatePv()
//
private ParameterValue createPv(String spaceSystem, String paramName,
long generationTime, long acquisitionTime, float value,
String monitoringResult) {
// create parameter definition
String parameterName = spaceSystem + paramName;
Parameter param = null;
if (xtceDb != null) {
param = xtceDb.getParameter(parameterName);
if (param == null) {
log.warn("Unable to get parameter " + parameterName
+ " from xtceDb.");
param = new Parameter(parameterName);
}
} else {
param = new Parameter(parameterName);
}
// set float type by default
ParameterType ptype = new FloatParameterType(paramName);
param.setParameterType(ptype);
// create parameter value
ParameterValue pv = new ParameterValue(param);
pv.setFloatValue((float) value);
// set monitoring result as specified in xml data (regardless of the
// alarms ranges)
String rangeCondition = null;
if (monitoringResult != null && monitoringResult.contains("_")) {
String[] parts = monitoringResult.split("_");
monitoringResult = parts[0];
rangeCondition = parts[1];
}
try {
if (monitoringResult != null) {
MonitoringResult mr = MonitoringResult
.valueOf(monitoringResult);
pv.setMonitoringResult(mr);
} else
pv.setMonitoringResult(MonitoringResult.DISABLED);
if (rangeCondition != null)
pv.setRangeCondition(RangeCondition.valueOf(rangeCondition));
} catch (Exception e) {
log.error("Unable to set the specified monitoring result (\""
+ monitoringResult
+ "\". Please check that the value is one of the Enum MonitoringResult (DISABLED, IN_LIMITS, WATCH, WATCH_LOW, WATCH_HIGH, WARNING, WARNING_LOW, WARNING_HIGH, DISTRESS, DISTRESS_LOW, DISTRESS_HIGH, CRITICAL, CRITICAL_LOW, CRITICAL_HIGH, SEVERE, SEVERE_LOW, SEVERE_HIGH)");
}
// set generation and acquisition time
pv.setGenerationTime(generationTime);
pv.setAcquisitionTime(acquisitionTime);
pv.setAcquisitionStatus(AcquisitionStatus.ACQUIRED);
return pv;
}
}