package org.yamcs.tctm; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamcs.ConfigurationException; import org.yamcs.InvalidIdentification; import org.yamcs.NoPermissionException; import org.yamcs.parameter.ParameterValue; import org.yamcs.TmProcessor; import org.yamcs.Processor; import org.yamcs.ProcessorException; import org.yamcs.YamcsException; import org.yamcs.YamcsServer; import org.yamcs.archive.PacketWithTime; import org.yamcs.archive.ReplayListener; import org.yamcs.archive.ReplayServer; import org.yamcs.archive.YarchReplay; import org.yamcs.cmdhistory.CommandHistoryProvider; import org.yamcs.cmdhistory.CommandHistoryRequestManager; import org.yamcs.commanding.PreparedCommand; import org.yamcs.parameter.ParameterProvider; import org.yamcs.parameter.ParameterRequestManager; import org.yamcs.parameter.ParameterRequestManagerImpl; import org.yamcs.parameter.ParameterValueWithId; import org.yamcs.parameter.ParameterWithIdConsumer; import org.yamcs.parameter.ParameterWithIdRequestHelper; import org.yamcs.protobuf.Commanding.CommandHistoryEntry; import org.yamcs.protobuf.Pvalue.ParameterData; import org.yamcs.protobuf.Yamcs.EndAction; import org.yamcs.protobuf.Yamcs.NamedObjectId; import org.yamcs.protobuf.Yamcs.NamedObjectList; import org.yamcs.protobuf.Yamcs.PacketReplayRequest; import org.yamcs.protobuf.Yamcs.PpReplayRequest; import org.yamcs.protobuf.Yamcs.ProtoDataType; import org.yamcs.protobuf.Yamcs.ReplayRequest; import org.yamcs.protobuf.Yamcs.ReplaySpeed; import org.yamcs.protobuf.Yamcs.ReplayStatus; import org.yamcs.protobuf.Yamcs.ReplayStatus.ReplayState; import org.yamcs.protobuf.Yamcs.TmPacketData; import org.yamcs.security.InvalidAuthenticationToken; import org.yamcs.security.SystemToken; import org.yamcs.xtce.Parameter; import org.yamcs.xtce.SequenceContainer; import org.yamcs.xtce.XtceDb; import org.yamcs.xtceproc.Subscription; import org.yamcs.xtceproc.XtceDbFactory; import org.yamcs.xtceproc.XtceTmProcessor; import com.google.common.util.concurrent.AbstractService; import io.protostuff.JsonIOUtil; /** * Provides telemetry packets and processed parameters from the yamcs archive. * * @author nm * */ public class ReplayService extends AbstractService implements ReplayListener, ArchiveTmPacketProvider, ParameterProvider, CommandHistoryProvider { static final long timeout=10000; boolean loop; long start, stop; // start and stop times of playback request String[] packets; // array of opsnames of packets to subscribe EndAction endAction; static Logger log=LoggerFactory.getLogger(ReplayService.class.getName()); ReplayRequest originalReplayRequest; private HashSet<Parameter> subscribedParameters=new HashSet<Parameter>(); private ParameterRequestManagerImpl parameterRequestManager; TmProcessor tmProcessor; volatile long dataCount=0; final XtceDb xtceDb; volatile long replayTime; private final String yamcsInstance; YarchReplay yarchReplay; Processor yprocessor; //the originalReplayRequest contains possibly only parameters. //the modified one sent to the ReplayServer contains the raw data required for extracting/processing those parameters ReplayRequest.Builder rawDataRequest; CommandHistoryRequestManager commandHistoryRequestManager; public ReplayService(String instance, ReplayRequest spec) throws ProcessorException, ConfigurationException { this.yamcsInstance = instance; this.originalReplayRequest = spec; xtceDb = XtceDbFactory.getInstance(instance); } public ReplayService(String instance, String config) throws ProcessorException, ConfigurationException { this.yamcsInstance = instance; ReplayRequest.Builder rrb = ReplayRequest.newBuilder(); try { JsonIOUtil.mergeFrom(config.getBytes(), rrb, org.yamcs.protobuf.SchemaYamcs.ReplayRequest.MERGE, false); } catch (IOException e) { throw new ConfigurationException("Cannot parse config into a replay request: "+e.getMessage(), e); } this.originalReplayRequest = rrb.build(); xtceDb = XtceDbFactory.getInstance(instance); } @Override public void init(Processor proc) throws ConfigurationException { this.yprocessor = proc; } @Override public void init(Processor proc, TmProcessor tmProcessor) { this.tmProcessor = tmProcessor; this.yprocessor = proc; } @Override public boolean isArchiveReplay() { return true; } @Override public void newData(ProtoDataType type, Object data) { switch(type) { case TM_PACKET: dataCount++; TmPacketData tpd = (TmPacketData)data; replayTime = tpd.getGenerationTime(); tmProcessor.processPacket(new PacketWithTime(tpd.getReceptionTime(), tpd.getGenerationTime(), tpd.getPacket().toByteArray())); break; case PP: parameterRequestManager.update((List<ParameterValue>)data); break; case CMD_HISTORY: CommandHistoryEntry che = (CommandHistoryEntry) data; commandHistoryRequestManager.addCommand(PreparedCommand.fromCommandHistoryEntry(che)); break; default: log.error("Unexpected data type {} received"); } } @Override public void stateChanged(ReplayStatus rs) { if(rs.getState()==ReplayState.CLOSED) { log.debug("End signal received"); notifyStopped(); tmProcessor.finished(); } else { yprocessor.notifyStateChange(); } } @Override public void doStop() { if(yarchReplay!=null) { yarchReplay.quit(); } notifyStopped(); } //finds out all raw data (TM and PP) required to provide the needed parameters. // in order to do this, subscribe to all parameters from the list, then check in the tmProcessor subscription which containers are needed // and in the subscribedParameters which PPs may be required private void createRawSubscription() throws YamcsException { rawDataRequest = originalReplayRequest.toBuilder().clearParameterRequest(); List<NamedObjectId> plist = originalReplayRequest.getParameterRequest().getNameFilterList(); if(plist.isEmpty()) { return; } ParameterWithIdRequestHelper pidrm = new ParameterWithIdRequestHelper(parameterRequestManager, new ParameterWithIdConsumer() { @Override public void update(int subscriptionId, List<ParameterValueWithId> params) {//ignore data, we create this subscription just to get the list of dependent containers and PPs } }); int subscriptionId; try { subscriptionId = pidrm.addRequest(plist, new SystemToken()); } catch (InvalidIdentification e) { NamedObjectList nol=NamedObjectList.newBuilder().addAllList(e.getInvalidParameters()).build(); throw new YamcsException("InvalidIdentification", "Invalid identification", nol); } catch (NoPermissionException e) { throw new RuntimeException("Unexpected No permission"); } XtceTmProcessor tmproc = yprocessor.getTmProcessor(); Subscription subscription = tmproc.getSubscription(); Collection<SequenceContainer> containers = subscription.getContainers(); if((containers==null)|| (containers.isEmpty())) { log.debug("No container required for the parameter subscription"); } else { PacketReplayRequest.Builder rawPacketRequest = originalReplayRequest.getPacketRequest().toBuilder(); for(SequenceContainer sc: containers) { rawPacketRequest.addNameFilter(NamedObjectId.newBuilder().setName(sc.getQualifiedName()).build()); } log.debug("after TM subscription, the request contains the following packets: "+rawPacketRequest.getNameFilterList()); rawDataRequest.setPacketRequest(rawPacketRequest); } pidrm.removeRequest(subscriptionId); //now check for PPs Set<String> pprecordings = new HashSet<>(); for(Parameter p: subscribedParameters) { pprecordings.add(p.getRecordingGroup()); } if(pprecordings.isEmpty()) { log.debug("No aadditional pp group added to the subscription"); } else { PpReplayRequest.Builder pprr = originalReplayRequest.getPpRequest().toBuilder(); pprr.addAllGroupNameFilter(pprecordings); rawDataRequest.setPpRequest(pprr.build()); } if(!rawDataRequest.hasPacketRequest() && !rawDataRequest.hasPpRequest()) { if(originalReplayRequest.hasParameterRequest()) { throw new YamcsException("Cannot find a replay source for any parmeters from request: "+originalReplayRequest.getParameterRequest().toString()); } else { throw new YamcsException("Refusing to create an empty replay request"); } } } private void createReplay() throws ProcessorException { ReplayServer replayServer = YamcsServer.getService(yamcsInstance, ReplayServer.class); if(replayServer==null) { throw new ProcessorException("ReplayServer not configured for this instance"); } try { yarchReplay = replayServer.createReplay(rawDataRequest.build(), this, new SystemToken()); } catch (YamcsException e) { log.error("Exception creating the replay", e); throw new ProcessorException("Exception creating the replay: "+e.getMessage(), e); } catch (InvalidAuthenticationToken e) { //should never come here throw new IllegalStateException(e); } } @Override public void doStart() { try { createRawSubscription(); createReplay(); } catch (YamcsException | ProcessorException e){ notifyFailed(e); return; } yarchReplay.start(); notifyStarted(); } @Override public void pause() { yarchReplay.pause(); } @Override public void resume() { yarchReplay.start(); } @Override public void seek(long time) { try { yarchReplay.seek(time); } catch (YamcsException e) { throw new RuntimeException(e); } } @Override public void setParameterListener(ParameterRequestManager parameterRequestManager) { this.parameterRequestManager = (ParameterRequestManagerImpl)parameterRequestManager; } @Override public void startProviding(Parameter paramDef) { synchronized(subscribedParameters) { subscribedParameters.add(paramDef); } } @Override public void startProvidingAll() { //TODO } @Override public void stopProviding(Parameter paramDef) { synchronized(subscribedParameters) { subscribedParameters.remove(paramDef); } } @Override public boolean canProvide(NamedObjectId id) { boolean result = false; Parameter p = xtceDb.getParameter(id); if(p!=null) { result= canProvide(p); } else { //check if it's system parameter if(XtceDb.isSystemParameter(id)) { result = true; } } return result; } @Override public boolean canProvide(Parameter p) { boolean result; if(xtceDb.getParameterEntries(p)!=null) { result = false; } else { result = true; } return result; } @Override public Parameter getParameter(NamedObjectId id) throws InvalidIdentification { Parameter p = xtceDb.getParameter(id); if(p==null) { throw new InvalidIdentification(); } else { return p; } } @Override public ReplaySpeed getSpeed() { return originalReplayRequest.getSpeed(); } @Override public ReplayRequest getReplayRequest() { return originalReplayRequest; } @Override public ReplayState getReplayState() { if(state() == State.NEW) { return ReplayState.INITIALIZATION; } else if(state() == State.FAILED) { return ReplayState.ERROR; } else { return yarchReplay.getState(); } } @Override public long getReplayTime() { return replayTime; } @Override public void changeSpeed(ReplaySpeed speed) { yarchReplay.changeSpeed(speed); // need to change the replay request to get the proper value when getReplayRequest() is called originalReplayRequest = originalReplayRequest.toBuilder().setSpeed(speed).build(); } @Override public void setCommandHistoryRequestManager(CommandHistoryRequestManager chrm) { this.commandHistoryRequestManager = chrm; } }