/** * Called for each value received from PV. * * <p>Base class remembers the <code>most_recent_value</code>, and asserts that one 'first' sample * is archived. Derived class <b>must</b> call <code>super()</code>. * * @param value Value received from PV * @return true if the value was already written because it's the first value after startup or * error, so there's no need to write that sample again. */ protected boolean handleNewValue(final VType value) { synchronized (this) { ++received_value_count; most_recent_value = value; } // NaN test if (value instanceof VNumber) { if (Double.isNaN(VTypeHelper.toDouble(value))) trouble_sample_log.log("'" + getName() + "': NaN " + VTypeHelper.toString(value)); } if (!enabled) return false; // Did we recover from write errors? if (need_write_error_sample && SampleBuffer.isInErrorState() == false) { need_write_error_sample = false; Activator.getLogger().log(Level.FINE, "Wrote error sample for {0}", getName()); addInfoToBuffer(ValueButcher.createWriteError()); need_first_sample = true; } // Is this the first sample after startup or an error? if (!need_first_sample) return false; need_first_sample = false; // Try to add as-is, but time stamp will be corrected to fit in final VType added = addInfoToBuffer(value); Activator.getLogger() .log(Level.FINE, "Wrote first sample for {0}: {1}", new Object[] {getName(), added}); return true; }
/** * Add given sample to buffer, performing a back-in-time check, updating the sample buffer error * state. * * @param value Value to archive * @return <code>false</code> if value failed back-in-time or future check, <code>true</code> if * value was added. */ protected final boolean addValueToBuffer(final VType value) { // Suppress samples that are too far in the future final Timestamp time = VTypeHelper.getTimestamp(value); if (isFuturistic(time)) { trouble_sample_log.log("'" + getName() + "': Futuristic " + value); return false; } synchronized (this) { if (last_archived_value != null && VTypeHelper.getTimestamp(last_archived_value).compareTo(time) >= 0) { // Cannot use this sample because of back-in-time problem. // Usually this is NOT an error: // We logged an initial sample, disconnected, disabled, ..., // and now we got an update from the IOC which still // carries the old, original time stamp of the PV, // and that's back in time... trouble_sample_log.log( getName() + " skips back-in-time:\n" + "last: " + VTypeHelper.toString(last_archived_value) + "\n" + "new : " + VTypeHelper.toString(value)); return false; } // else ... last_archived_value = value; } buffer.add(value); if (SampleBuffer.isInErrorState()) need_write_error_sample = true; return true; }
@Test public void testCSVImport() throws Exception { final InputStream input = getClass().getResourceAsStream("Lakeshore_A_9_2011.xml"); final SampleImporter importer = new CSVSampleImporter(); final List<VType> values = importer.importValues(input); assertTrue(values.size() > 0); for (VType value : values) System.out.println(VTypeHelper.toString(value)); final String text = VTypeHelper.toString(values.get(values.size() - 1)); assertThat(text, containsString("2011-09-13")); assertThat(text, containsString("08:57:44.968")); assertThat(text, containsString("84.912")); }
/** * Add given info value to buffer, tweaking its time stamp if necessary * * @param value Value to archive * @return Value that was actually added, which may have adjusted time stamp */ protected final VType addInfoToBuffer(VType value) { synchronized (this) { if (last_archived_value != null) { final Timestamp last = VTypeHelper.getTimestamp(last_archived_value); if (last.compareTo(VTypeHelper.getTimestamp(value)) >= 0) { // Patch the time stamp final Timestamp next = last.plus(TimeDuration.ofMillis(100)); value = VTypeHelper.transformTimestamp(value, next); } // else: value is OK as is } } addValueToBuffer(value); return value; }
/** * Check a received value for basic problems before passing it on to the sample mechanism * * @param value Value as received from network layer * @return Value to be used for archive */ private VType checkReceivedValue(VType value) { if (value instanceof Time) { try { final Time time = (Time) value; // Invoke time.getTimestamp() to detect RuntimeError in VType 2013/11/01 if (time.isTimeValid() && time.getTimestamp() != null) return value; else { trouble_sample_log.log("'" + getName() + "': Invalid time stamp "); value = VTypeHelper.transformTimestamp(value, Timestamp.now()); } } catch (RuntimeException ex) { Logger.getLogger(getClass().getName()) .log(Level.WARNING, "'" + getName() + "': Exception getting time stamp", ex); value = VTypeHelper.transformTimestamp(value, Timestamp.now()); } } else trouble_sample_log.log("'" + getName() + "': Received no time information for " + value); return value; }
/** Add one(!) 'disconnected' sample */ private void logDisconnected() { synchronized (samples) { final int size = samples.getSize(); if (size > 0) { final String last = VTypeHelper.getMessage(samples.getSample(size - 1).getValue()); // Does last sample already have 'disconnected' status? if (Messages.Model_Disconnected.equals(last)) return; } samples.addLiveSample(new PlotSample(Messages.LiveData, Messages.Model_Disconnected)); } }
/** Enable or disable groups based on received value */ private final void handleEnablement(final VType value) { if (enablement == Enablement.Passive) throw new Error("Not to be called when passive"); // $NON-NLS-1$ // Get boolean value (true <==> >0.0) final double number = VTypeHelper.toDouble(value); final boolean yes = number > 0.0; // Do we enable or disable based on that value? final boolean enable = enablement == Enablement.Enabling ? yes : !yes; // Check which group needs to _change_ for (ArchiveGroup group : groups) { if (group.isEnabled() != enable) group.enable(enable); } }
/** Update the enablement state in case of change */ private final void updateEnabledState(final boolean new_enabled_state) { // Any change? if (new_enabled_state == enabled) return; enabled = new_enabled_state; // In case this arrived after shutdown, don't log it. if (!is_running) return; if (enabled) { // If we have the 'current' value of the PV... VType value; synchronized (this) { value = most_recent_value; } if (value != null) { // Add to the buffer with timestamp 'now' to show // the re-enablement value = VTypeHelper.transformTimestampToNow(value); addValueToBuffer(value); } } else addInfoToBuffer(ValueButcher.createDisabled()); }
/** {@inheritDoc} */ @Override public VType next() throws Exception { if (index < 0) throw new Exception("End of samples"); // $NON-NLS-1$ // Remember value, prepare the next value final VType result = value; samples.getLock().lock(); try { ++index; if (index >= samples.size()) index = -1; // No more samples else { value = samples.get(index).getVType(); if (VTypeHelper.getTimestamp(value).compareTo(end) > 0) index = -1; // Beyond end time } } finally { samples.getLock().unlock(); } return result; }
/** {@inheritDoc} */ @Override protected void performExport(final IProgressMonitor monitor, final PrintStream out) throws Exception { final DateFormat date_format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS"); int count = 0; for (ModelItem item : model.getItems()) { // Item header if (count > 0) out.println(); printItemInfo(out, item); // Get data monitor.subTask(NLS.bind("Fetching data for {0}", item.getName())); final ValueIterator values = createValueIterator(item); // Dump all values MatlabQualityHelper qualities = new MatlabQualityHelper(); long line_count = 0; out.println("clear t;"); out.println("clear v;"); out.println("clear q;"); while (values.hasNext() && !monitor.isCanceled()) { final VType value = values.next(); ++line_count; // t(1)='2010/03/15 13:30:10.123'; out.println( "t{" + line_count + "}='" + date_format.format(VTypeHelper.getTimestamp(value).toDate()) + "';"); // v(1)=4.125; final double num = VTypeHelper.toDouble(value); if (Double.isNaN(num) || Double.isInfinite(num)) out.println("v(" + line_count + ")=NaN;"); else out.println("v(" + line_count + ")=" + num + ";"); // q(1)=0; out.println( "q(" + line_count + ")=" + qualities.getQualityCode( VTypeHelper.getSeverity(value), VTypeHelper.getMessage(value)) + ";"); if (line_count % PROGRESS_UPDATE_LINES == 0) monitor.subTask(NLS.bind("{0}: Wrote {1} samples", item.getName(), line_count)); } out.println(comment + "Convert time stamps into 'date numbers'"); out.println("tn=datenum(t, 'yyyy/mm/dd HH:MM:SS.FFF');"); out.println(comment + "Prepare patched data because"); out.println(comment + "timeseries() cannot handle duplicate time stamps"); out.println("[xx, idx]=unique(tn, 'last');"); out.println("pt=tn(idx);"); out.println("pv=v(idx);"); out.println("pq=q(idx);"); out.println("clear xx idx"); out.println(comment + "Convert into time series and plot"); // Patch "_" in name because Matlab plot will interprete it as LaTeX sub-script final String channel_name = item.getDisplayName().replace("_", "\\_"); out.println( "channel" + count + "=timeseries(pv', pt', pq', 'IsDatenum', true, 'Name', '" + channel_name + "');"); out.print("channel" + count + ".QualityInfo.Code=["); for (int q = 0; q < qualities.getNumCodes(); ++q) out.print(" " + q); out.println(" ];"); out.print("channel" + count + ".QualityInfo.Description={"); for (int q = 0; q < qualities.getNumCodes(); ++q) out.print(" '" + qualities.getQuality(q) + "'"); out.println(" };"); out.println(); ++count; } out.println(comment + "Example for plotting the data"); for (int i = 0; i < count; ++i) { out.println("subplot(1, " + count + ", " + (i + 1) + ");"); out.println("plot(channel" + i + ");"); } }
/** @return Last value written to archive */ public final synchronized String getLastArchivedValue() { return VTypeHelper.toString(last_archived_value); }
/** @return Most recent value of the channel's PV */ public final synchronized String getCurrentValue() { return VTypeHelper.toString(most_recent_value); }
/** {@inheritDoc} */ @Override public VType next() throws Exception { final VType result = samples[index]; ++index; if (index < samples.length) return result; // Prepare next batch of samples fetch(VTypeHelper.getTimestamp(result)); if (samples == null) return result; // Inspect next batch of samples // In most cases, this fetch should return the 'result' again: // some_timestamp value A // last_timestamp value B <-- last_sample of previous batch // new_timestamp value C // Since we ask from at-or-before last_timestamp on, // we get the last_sample once more and need to skip it. // // But also consider the following situation, where the last batch ended // in a range of data that had the same time stamp, and even some same // values: // some_timestamp value A // last_timestamp value B // last_timestamp value C // last_timestamp value C // last_timestamp value C <-- last_sample of previous batch // last_timestamp value C // last_timestamp value C // last_timestamp value D // last_timestamp value E // new_timestamp value F // Reasons for same timestamps: Stuck IOC clock, // or sequences like .. Repeat N, next value, Disconnected, Arch. Off. // Reason for the same value: General mess-up. // // When we request new data from 'last_sample.getTime()' on, // i.e. from last_timestamp on, we could get any of the values B to E, // since they're all stamped at-or-before last_timestamp. // Which one exactly depends on optimization inside the data server. // From the end of the new samples, go backward: for (index = samples.length - 1; index >= 0; --index) { // If we find the previous batch's last sample... if (samples[index].equals(result)) // Skip all the samples up to and including it break; } // Nothing to skip? Return as is. if (index < 0) index = 0; // Nothing left? Clear samples. if (index >= samples.length - 1) samples = null; return result; }
/** @param value Value to log with 'now' as time stamp */ private void logValueAsNow(final VType value) { if (value == null) logDisconnected(); else // Transform value to have 'now' as time stamp samples.addLiveSample(VTypeHelper.transformTimestampToNow(value)); }