package org.signalml.app.worker.monitor;
import static org.signalml.app.util.i18n.SvarogI18n._;
import static org.signalml.app.util.i18n.SvarogI18n._R;
import java.beans.PropertyChangeEvent;
import java.util.List;
import multiplexer.jmx.client.IncomingMessageData;
import multiplexer.jmx.client.JmxClient;
import multiplexer.protocol.Protocol.MultiplexerMessage;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.signalml.app.model.document.opensignal.ExperimentDescriptor;
import org.signalml.app.model.signal.PagingParameterDescriptor;
import org.signalml.app.view.common.dialogs.BusyDialog;
import org.signalml.app.view.common.dialogs.errors.Dialogs;
import org.signalml.app.view.common.dialogs.errors.Dialogs.DIALOG_OPTIONS;
import org.signalml.app.worker.SwingWorkerWithBusyDialog;
import org.signalml.domain.signal.samplesource.RoundBufferMultichannelSampleSource;
import org.signalml.domain.tag.MonitorTag;
import org.signalml.domain.tag.StyledMonitorTagSet;
import org.signalml.domain.tag.TagStylesGenerator;
import org.signalml.multiplexer.protocol.SvarogConstants.MessageTypes;
import org.signalml.multiplexer.protocol.SvarogProtocol;
import org.signalml.multiplexer.protocol.SvarogProtocol.Sample;
import org.signalml.multiplexer.protocol.SvarogProtocol.SampleVector;
import org.signalml.plugin.export.signal.SignalSelectionType;
import org.signalml.plugin.export.signal.TagStyle;
import org.signalml.util.FormatUtils;
import com.google.protobuf.ByteString;
/** MonitorWorker
*
*/
public class MonitorWorker extends SwingWorkerWithBusyDialog<Void, Object> {
public static String OPENING_MONITOR_CANCELLED = "openingMonitorCanceled";
public static final int TIMEOUT_MILIS = 5000;
protected static final Logger logger = Logger.getLogger(MonitorWorker.class);
private final JmxClient client;
private final RoundBufferMultichannelSampleSource sampleSource;
private final StyledMonitorTagSet tagSet;
private volatile boolean finished;
/**
* This object is responsible for recording tags received by this {@link MonitorWorker}.
* It is connected to the monitor by an external object using
* {@link MonitorWorker#connectTagRecorderWorker(org.signalml.app.worker.monitor.TagRecorder)}.
*/
private TagRecorder tagRecorderWorker;
/**
* This object is responsible for recording signal received by this {@link MonitorWorker}.
* It is connected to the monitor by an external object using
* {@link MonitorWorker#connectSignalRecorderWorker(org.signalml.app.worker.monitor.SignalRecorderWorker) }.
*/
private SignalRecorderWorker signalRecorderWorker;
/**
* Styles generator for monitor tags.
*/
private TagStylesGenerator stylesGenerator;
private ExperimentDescriptor experimentDescriptor;
public MonitorWorker(ExperimentDescriptor experimentDescriptor, RoundBufferMultichannelSampleSource sampleSource, StyledMonitorTagSet tagSet) {
super(null);
this.experimentDescriptor = experimentDescriptor;
this.client = experimentDescriptor.getJmxClient();
this.sampleSource = sampleSource;
this.tagSet = tagSet;
//TODO blocksPerPage - is that information sent to the monitor worker? Can we substitute default PagingParameterDescriptor.DEFAULT_BLOCKS_PER_PAGE
//with a real value?
stylesGenerator = new TagStylesGenerator(experimentDescriptor.getSignalParameters().getPageSize(), PagingParameterDescriptor.DEFAULT_BLOCKS_PER_PAGE);
getBusyDialog().setText(_("Waiting for the first sample..."));
logger.setLevel((Level) Level.INFO);
}
@Override
protected Void doInBackground() {
MultiplexerMessage sampleMsg;
boolean firstSampleReceived = false;
showBusyDialog();
while (!isCancelled()) {
try {
IncomingMessageData msgData;
if (firstSampleReceived)
msgData = client.receive(TIMEOUT_MILIS);
else {
/**
* The first sample to be received is the most problematic
* - sometimes (when starting new experiments) the multiplexer
* has not been started yet at this point, so the receive
* timeout should longer in order to wait for the multiplexer
* to fully start to operate.
*/
msgData = client.receive();
firstSampleReceived = true;
hideBusyDialog();
}
if (isCancelled()) {
//it might have been cancelled while waiting on the client.receive()
return null;
}
else if (msgData == null) {
// timeout
double timeoutInSeconds = ((double)TIMEOUT_MILIS) / 1000.0;
DIALOG_OPTIONS result = Dialogs.showWarningYesNoDialog(_R("Did not receive any samples in {0} seconds, maybe the experiment is down?\nDo you wish to wait some more? Press No to disconnect from the experiment.", FormatUtils.format(timeoutInSeconds)));
if (result == DIALOG_OPTIONS.YES) {
continue;
} else {
DisconnectFromExperimentWorker disconnectWorker = new DisconnectFromExperimentWorker(experimentDescriptor);
disconnectWorker.execute();
break;
}
}
else {
sampleMsg = msgData.getMessage();
}
} catch (InterruptedException e) {
logger.error("receive failed", e);
return null;
}
int sampleType = sampleMsg.getType();
logger.debug("Worker: received message type: " + sampleType);
switch (sampleType) {
case MessageTypes.AMPLIFIER_SIGNAL_MESSAGE:
parseMessageWithSamples(sampleMsg.getMessage());
break;
case MessageTypes.TAG:
parseMessageWithTags(sampleMsg.getMessage());
break;
default:
final String name = MessageTypes.instance.getConstantsNames().get(sampleType);
logger.error("received unknown reply: " + sampleType + "/" + name);
}
}
return null;
}
/**
* If the message contains samples, this function can be used
* to parse its contents.
* @param sampleMsgString the content of the message
*/
protected void parseMessageWithSamples(ByteString sampleMsgString) {
logger.debug("Worker: reading chunk!");
SampleVector sampleVector;
try {
sampleVector = SampleVector.parseFrom(sampleMsgString);
} catch (Exception e) {
logger.error("", e);
return;
}
List<Sample> samples = sampleVector.getSamplesList();
for (int k=0; k<sampleVector.getSamplesCount(); k++) {
Sample sample = sampleVector.getSamples(k);
float[] newSamplesArray = new float[sample.getChannelsCount()];
for (int i = 0; i < newSamplesArray.length; i++) {
newSamplesArray[i] = sample.getChannels(i);
}
double samplesTimestamp = samples.get(0).getTimestamp();
//System.out.println(" -- " + samplesTimestamp);
NewSamplesData newSamplesPackage = new NewSamplesData(newSamplesArray, samplesTimestamp);
publish(newSamplesPackage);
}
}
/**
* If the given message contains tags, this method can be used
* to parse its contents.
* @param sampleMsgString the contents of the message
*/
private void parseMessageWithTags(ByteString sampleMsgString) {
logger.info("Tag recorder: got a tag!");
final SvarogProtocol.Tag tagMsg;
try {
tagMsg = SvarogProtocol.Tag.parseFrom(sampleMsgString);
} catch (Exception e) {
logger.error("", e);
return;
}
// TODO: By now we ignore field channels and assume that tag if for all channels
final double tagLen = tagMsg.getEndTimestamp() - tagMsg.getStartTimestamp();
TagStyle style = tagSet.getStyle(SignalSelectionType.CHANNEL, tagMsg.getName());
if (style == null) {
style = stylesGenerator.getSmartStyleFor(tagMsg.getName(), tagLen, -1);
tagSet.addStyle(style);
}
final MonitorTag tag = new MonitorTag(style,
tagMsg.getStartTimestamp(),
tagLen,
-1);
for (SvarogProtocol.Variable v : tagMsg.getDesc().getVariablesList()) {
if (v.getKey().equals("annotation")) {
tag.setAnnotation(v.getValue());
}
else {
tag.addAttributeToTag(v.getKey(), v.getValue());
}
}
publish(tag);
}
@Override
protected void process(List<Object> objs) {
for (Object o : objs) {
if (o instanceof NewSamplesData) {
NewSamplesData data = (NewSamplesData) o;
sampleSource.lock();
tagSet.lock();
sampleSource.addSamples(data.getSampleValues());
tagSet.newSample(data.getSamplesTimestamp());
tagSet.unlock();
sampleSource.unlock();
// set first sample timestamp for the tag recorder
if (tagRecorderWorker != null && !tagRecorderWorker.isStartRecordingTimestampSet()) {
tagRecorderWorker.setStartRecordingTimestamp(data.getSamplesTimestamp());
}
// sends chunks to the signal recorder
if (signalRecorderWorker != null) {
signalRecorderWorker.offerChunk(data.getSampleValues());
if (!signalRecorderWorker.isFirstSampleTimestampSet())
signalRecorderWorker.setFirstSampleTimestamp(data.getSamplesTimestamp());
}
} else {
MonitorTag tag = (MonitorTag) o;
tagSet.lock();
tagSet.addTag(tag);
tagSet.unlock();
//record tag
if (tagRecorderWorker != null) {
tagRecorderWorker.offerTag(tag);
}
firePropertyChange("newTag", null, (MonitorTag) o);
}
}
}
@Override
protected void done() {
super.done();
finished = true;
firePropertyChange("tagsRead", null, tagSet);
}
public StyledMonitorTagSet getTagSet() {
return tagSet;
}
public boolean isFinished() {
return finished;
}
/**
* Sets the {@link TagRecorder} to which the tags will be sent by this
* {@link MonitorWorker}. Setting a {@link TagRecorder} using this method
* starts sending all tags received by this {@link MonitorWorker} to the
* given {@link TagRecorder}.
*
* @param tagRecorderWorker the {@link TagRecorder} responsible for recording
* the tags from this {@link MonitorWorker}.
*/
public void connectTagRecorderWorker(TagRecorder tagRecorderWorker) {
this.tagRecorderWorker = tagRecorderWorker;
}
/**
* Allows to disconnect a {@link TagRecorder} which was connected using
* {@link MonitorWorker#connectTagRecorderWorker(org.signalml.app.worker.monitor.TagRecorder)}.
* No more tags are sent to the {@link TagRecorder} after disconnecting.
*/
public void disconnectTagRecorderWorker() {
this.tagRecorderWorker = null;
}
/**
* Sets the {@link SignalRecorderWorker} to which signal will be sent by this
* {@link MonitorWorker}. Setting a {@link SignalRecorderWorker} using this method
* starts sending signal received by this {@link MonitorWorker} to the
* given SignalRecorderWorker.
*
* @param signalRecorderWorker the {@link SignalRecorderWorker} responsible for recording
* signal from this {@link MonitorWorker}.
*/
public void connectSignalRecorderWorker(SignalRecorderWorker signalRecorderWorker) {
this.signalRecorderWorker = signalRecorderWorker;
}
/**
* Disconnects a {@link SignalRecorderWorker} which was connected using
* {@link MonitorWorker#connectSignalRecorderWorker(org.signalml.app.worker.monitor.SignalRecorderWorker) }.
* Signal is no longer sent to the {@link SignalRecorderWorker} after disconnecting.
*/
public void disconnectSignalRecorderWorker() {
this.signalRecorderWorker = null;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
super.propertyChange(evt);
if (BusyDialog.CANCEL_BUTTON_PRESSED.equals(evt.getPropertyName())) {
cancel(true);
firePropertyChange(MonitorWorker.OPENING_MONITOR_CANCELLED, false, true);
}
}
/**
* Converts milliseconds to a String representing date
* - for debugging purposes
public static String msToDate(double timestamp) {
long mili = (long) (timestamp * 1000);
SimpleDateFormat sdf = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss,SSS");
Date resultdate = new Date(mili);
return(sdf.format(resultdate));
}*/
}
/**
* This class holds information about the newest samples package that was received
* by Svarog and published by the doInTheBackground method.
*
* The data consists of the sample values (a sample value for each channel)
* and a timestamp of these samples.
* @author Piotr Szachewicz
*/
class NewSamplesData {
/**
* The values of the samples. The size of the array is equal to the number
* of channels in the signal.
*/
private float[] sampleValues;
/**
* The timestamp of the samples represented by the sampleValues array.
*/
private double samplesTimestamp;
/**
* Constructor. Creates an object containing samples data.
* @param sampleValues the values of the samples for each channel
* @param samplesTimestamp the timestamp of the samples
*/
public NewSamplesData(float[] sampleValues, double samplesTimestamp) {
this.sampleValues = sampleValues;
this.samplesTimestamp = samplesTimestamp;
}
public float[] getSampleValues() {
return sampleValues;
}
public void setSampleValues(float[] sampleValues) {
this.sampleValues = sampleValues;
}
public double getSamplesTimestamp() {
return samplesTimestamp;
}
public void setSamplesTimestamp(double samplesTimestamp) {
this.samplesTimestamp = samplesTimestamp;
}
}