Exemplo n.º 1
0
 public void ActivateDetector(Detector det = null)
 {
     if (!file)
     {
         DAQControl.ActivateDetector(det);
     }
 }
Exemplo n.º 2
0
        public void SetupEventHandlers()
        {
            LMLoggers.LognLM applog = NC.App.Loggers.Logger(LMLoggers.AppSection.App);
            /// set up the 7 magic event handlers
            /// here I have a base handler that does the same thing for every event (writes a string to the log)
            SetEventHandler(ActionEvents.EventType.PreAction, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.PreAction, applog, LogLevels.Verbose, o);
            });
            SetEventHandler(ActionEvents.EventType.ActionPrep, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionPrep, applog, LogLevels.Verbose, o);
            });
            SetEventHandler(ActionEvents.EventType.ActionStart, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionStart, applog, LogLevels.Verbose, o);
            });
            SetEventHandler(ActionEvents.EventType.ActionInProgress, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionInProgress, applog, LogLevels.Verbose, o);
            });
            SetEventHandler(ActionEvents.EventType.ActionStop, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionStop, applog, LogLevels.Warning, o);
            });
            SetEventHandler(ActionEvents.EventType.ActionCancel, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionCancel, applog, LogLevels.Warning, o);
            });
            SetEventHandler(ActionEvents.EventType.ActionFinished, (object o) =>
            {
                string s           = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionFinished, applog, LogLevels.Info, o);
                NC.App.Opstate.SOH = NCC.OperatingState.Stopped;  // in case we got here after a Cancel
                NC.App.Loggers.Flush();
            });

            NC.App.Opstate.SOH = NCC.OperatingState.Stopped;
        }
Exemplo n.º 3
0
        /// <summary>
        /// Performs an assay operation.
        /// </summary>
        /// <param name="measurement">The measurement.</param>
        /// <param name="cancellationToken">The cancellation token to observe.</param>
        /// <exception cref="Ptr32Exception">An error occurred communicating with the device.</exception>
        protected void PerformAssay(Measurement measurement, CancellationToken cancellationToken)
        {
            System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
            try
            {
                m_logger.TraceEvent(LogLevels.Info, 0, "PTR-32[{0}]: Started assay", DeviceName);
                m_logger.Flush();

                if (m_setvoltage)
                {
                    SetVoltage(m_voltage, MaxSetVoltageTime, CancellationToken.None);
                }

                Stopwatch stopwatch = new Stopwatch();
                TimeSpan  duration  = TimeSpan.FromSeconds(measurement.AcquireState.lm.Interval);
                byte[]    buffer    = new byte[1024 * 1024];
                long      total     = 0;

                m_device.Reset();
                stopwatch.Start();
                m_logger.TraceEvent(LogLevels.Verbose, 11901, "{0} start", DateTime.Now.ToString());
                while (stopwatch.Elapsed < duration)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    if (m_device.Available > 0)
                    {
                        int bytesRead = m_device.Read(buffer, 0, buffer.Length);

                        if (bytesRead > 0)
                        {
                            RDT.PassBufferToTheCounters(buffer, 0, bytesRead);
                            total += bytesRead;
                        }
                    }
                }

                stopwatch.Stop();
                m_logger.TraceEvent(LogLevels.Verbose, 11901, "{0} stop", DateTime.Now.ToString());

                lock (m_monitor) {
                    m_cancellationTokenSource.Dispose();
                    m_cancellationTokenSource = null;
                }

                m_logger.TraceEvent(LogLevels.Info, 0,
                                    "PTR-32[{0}]: Finished assay; read {1} bytes in {2}s",
                                    DeviceName, total, stopwatch.Elapsed.TotalSeconds);
                m_logger.Flush();
                DAQControl.HandleEndOfCycleProcessing(this, new Analysis.StreamStatusBlock(@"PTR32 Done"));
            }
            catch (OperationCanceledException) {
                m_logger.TraceEvent(LogLevels.Info, 0, "PTR-32[{0}]: Stopping assay", DeviceName);
                m_logger.Flush();
                DAQControl.StopActiveAssayImmediately();
                throw;
            }
            catch (Exception ex) {
                m_logger.TraceEvent(LogLevels.Error, 0, "PTR-32[{0}]: Error during assay: {1}", DeviceName, ex.Message);
                m_logger.TraceException(ex, true);
                m_logger.Flush();
                DAQControl.HandleFatalGeneralError(this, ex);
                throw;
            }
        }
Exemplo n.º 4
0
        /// <summary>
        /// Performs a single cycle assay operation.
        /// </summary>
        /// <param name="measurement">The measurement parameters needed for the op.</param>
        /// <param name="cancellationToken">The cancellation token to observe.</param>
        /// <exception cref="MCADeviceLostConnectionException">An error occurred communicating with the device.</exception>
        /// <exception cref="MCADeviceBadDataException">An error occurred with the raw data stream state.</exception>
        /// <exception cref="Exception">Empty or corrupt runtime data structure.</exception>
        protected void PerformAssay(Measurement measurement, MeasTrackParams mparams, CancellationToken cancellationToken)
        {
            MCA527ProcessingState ps = (MCA527ProcessingState)(RDT.State);

            Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;

            try
            {
                if (ps == null)
                {
                    throw new Exception("Big L bogus state");                      // caught cleanly below
                }
                if (ps.writingFile && (ps.file == null || ps.file.writer == null))
                {
                    m_logger.TraceEvent(LogLevels.Verbose, 9, "null");
                }
                ushort seq = mparams.seq;
                m_logger.TraceEvent(LogLevels.Info, 0, "MCA527[{0}]: Started assay {1}", DeviceName, seq);
                m_logger.Flush();

                cancellationToken.ThrowIfCancellationRequested();

                if (m_setvoltage)
                {
                    SetVoltage(m_voltage, MaxSetVoltageTime, cancellationToken);
                }

                cancellationToken.ThrowIfCancellationRequested();

                if (seq == 1)                  // init var on first cycle
                {
                    ps.device = m_device;
                }

                bool x = InitSettings(seq, (uint)mparams.interval);                  // truncate for discrete integer result

                Stopwatch stopwatch = new Stopwatch();
                TimeSpan  duration  = TimeSpan.FromSeconds((uint)mparams.interval);
                byte[]    buffer    = new byte[1024 * 1024];

                // a5 5a 42 00 01 00 ae d5 44 56 b9 9b
                // flags: 0x0001 => spectrum is cleared and a new start time is set
                // start time: 0x5644d5ae => seconds since Dec 31, 1969, 16:00:00 GMT
                uint        secondsSinceEpoch = (uint)(Math.Abs(Math.Round((DateTime.UtcNow - MCADevice.MCA527EpochTime).TotalSeconds)));
                MCAResponse response          = m_device.Client.Send(MCACommand.Start(StartFlag.SpectrumClearedNewStartTime,
                                                                                      false, false, false, secondsSinceEpoch));
                if (response == null)
                {
                    throw new MCADeviceLostConnectionException();
                }

                const uint CommonMemoryBlockSize = 1440;
                // what's the most that could be left over from a previous attempt to decode? => 3 bytes
                byte[]  rawBuffer        = new byte[CommonMemoryBlockSize + 3];
                ulong[] timestampsBuffer = new ulong[CommonMemoryBlockSize + 1];

                uint commonMemoryReadIndex = 0;
                uint rawBufferOffset       = 0;

                m_logger.TraceEvent(LogLevels.Verbose, 11901, "{0} start time for {1}", DateTime.Now.ToString(), seq);

                ulong    accumulatedTime = 0, totalEvents = 0;
                int      maxindex = 0;
                TimeSpan elapsed  = new TimeSpan(0);
                stopwatch.Start();
                while (true)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    QueryState527ExResponse qs527er = (QueryState527ExResponse)m_device.Client.Send(MCACommand.QueryState527Ex());
                    if (qs527er == null)
                    {
                        throw new MCADeviceLostConnectionException();
                    }

                    MCAState state = qs527er.MCAState;

                    // pull off some data while we are waiting...
                    uint commonMemoryFillLevel = qs527er.CommonMemoryFillLevel;
                    uint bytesAvailable        = commonMemoryFillLevel - commonMemoryReadIndex;

                    elapsed = stopwatch.Elapsed;                      // snapshot

                    if (state != MCAState.Run && bytesAvailable == 0) // requested time is complete
                    {
                        break;
                    }
                    if ((elapsed = stopwatch.Elapsed) > duration)                       // elapsed time is complete
                    {
                        break;
                    }
                    if (bytesAvailable >= CommonMemoryBlockSize)                       // a full data block
                    {
                        QueryCommonMemoryResponse qcmr = (QueryCommonMemoryResponse)m_device.Client.Send(MCACommand.QueryCommonMemory(commonMemoryReadIndex / 2));
                        if (qcmr == null)
                        {
                            throw new MCADeviceLostConnectionException();
                        }
                        // bytesToCopy needs to always be even, so that commonMemoryReadIndex always stays even...
                        uint bytesToCopy = Math.Min(bytesAvailable / 2, CommonMemoryBlockSize / 2) * 2;
                        qcmr.CopyData(0, rawBuffer, (int)rawBufferOffset, (int)bytesToCopy);

                        if (ps.writingFile && ps.file != null && ps.file.writer != null)                         // write the block
                        {
                            ps.file.WriteTimestampsRawDataChunk(rawBuffer, 0, (int)bytesToCopy);
                        }

                        rawBufferOffset       += bytesToCopy;
                        commonMemoryReadIndex += bytesToCopy;
                        uint timestampsCount = m_device.TransformRawData(rawBuffer, ref rawBufferOffset, timestampsBuffer);

                        // make sure rawBufferOffset is never greater than 3 after transforming data
                        // => means something has gone wrong
                        if (rawBufferOffset > 3)
                        {
                            throw new MCADeviceBadDataException();
                        }

                        // copy the data out...
                        if (timestampsCount > 0)
                        {
                            if (maxindex < RDT.State.maxValuesInBuffer)                             // accumulate
                            {
                                if (RDT.State.timeArray.Count < maxindex + timestampsCount)
                                {
                                    RDT.State.timeArray.AddRange(new ulong[timestampsCount]);
                                }
                                for (int i = 0; i < timestampsCount; i++)
                                {
                                    accumulatedTime += timestampsBuffer[i];
                                    RDT.State.timeArray[maxindex] = accumulatedTime;
                                    maxindex++;                                      // always 1 more than the last index
                                }
                                RDT.State.NumValuesParsed     += timestampsCount;
                                RDT.State.hitsPerChn[0]       += timestampsCount;
                                RDT.State.NumTotalsEncountered = RDT.State.NumValuesParsed; // only one channel
                            }
                            else                                                            // process
                            {
                                long _max = RDT.State.NumValuesParsed;
                                if (_max < RDT.State.neutronEventArray.Count)                                  // equalize the length of the empty neutron channel event list
                                {
                                    RDT.State.neutronEventArray.RemoveRange((int)_max, RDT.State.neutronEventArray.Count - (int)_max);
                                }
                                else if (_max > RDT.State.neutronEventArray.Count)
                                {
                                    RDT.State.neutronEventArray.AddRange(new uint[_max - RDT.State.neutronEventArray.Count]);
                                }
                                if (_max < RDT.State.timeArray.Count)
                                {
                                    RDT.State.timeArray.RemoveRange((int)_max, RDT.State.timeArray.Count - (int)_max);
                                }
                                else if (_max > RDT.State.timeArray.Count)
                                {
                                    RDT.State.timeArray.AddRange(new ulong[_max - RDT.State.timeArray.Count]);
                                }
                                m_logger.TraceEvent(LogLevels.Verbose, 88, "{0} {1} handling {2} timestampsCount {3} num", elapsed, duration, timestampsCount, RDT.State.NumValuesParsed);
                                RDT.PassBufferToTheCounters((int)_max);
                                maxindex = 0; totalEvents += RDT.State.NumTotalsEncountered;
                                RDT.StartNewBuffer();
                            }
                        }
                    }
                    else if (bytesAvailable > 0 && state != MCAState.Run)
                    {
                        // special case for when there's not a whole block left to read
                        // we can only read up to the address: CommonMemorySize - 1440
                        uint commonMemorySize = qs527er.CommonMemorySize;

                        uint readAddress = commonMemoryReadIndex;
                        uint readOffset  = 0;
                        if (readAddress > commonMemorySize - 1440)
                        {
                            readOffset   = readAddress - (commonMemorySize - 1440);
                            readAddress -= readOffset;
                        }

                        QueryCommonMemoryResponse qcmr = (QueryCommonMemoryResponse)m_device.Client.Send(MCACommand.QueryCommonMemory(readAddress / 2));
                        if (qcmr == null)
                        {
                            throw new MCADeviceLostConnectionException();
                        }
                        uint bytesToCopy = bytesAvailable;
                        qcmr.CopyData((int)readOffset, rawBuffer, (int)rawBufferOffset, (int)bytesToCopy);

                        if (ps.writingFile && ps.file != null && ps.file.writer != null)                         // write the block
                        {
                            ps.file.WriteTimestampsRawDataChunk(rawBuffer, (int)readOffset, (int)bytesToCopy);
                        }

                        rawBufferOffset       += bytesToCopy;
                        commonMemoryReadIndex += bytesToCopy;
                        uint timestampsCount = m_device.TransformRawData(rawBuffer, ref rawBufferOffset, timestampsBuffer);

                        //if (rawBufferOffset > 0) {
                        // apparently this can happen. Perhaps when the device gets cut off (because of a timer event), right in the middle of writing?
                        //throw new MCADeviceBadDataException();
                        // an Engineer from GBS said we are running on a very old firmware version,
                        // perhaps that has something to do with it...
                        //}
                        if (timestampsCount > 0)
                        {
                            // copy the timestampsBuffer value into the RDT.State.timeArray, Q: wait to fill a much large internal buffer before calling the transform?
                            if (maxindex < RDT.State.maxValuesInBuffer)                             // accumulate
                            {
                                if (RDT.State.timeArray.Count < maxindex + timestampsCount)
                                {
                                    RDT.State.timeArray.AddRange(new ulong[timestampsCount]);
                                }
                                for (int i = 0; i < timestampsCount; i++)
                                {
                                    accumulatedTime += timestampsBuffer[i];
                                    RDT.State.timeArray[maxindex] = accumulatedTime;
                                    maxindex++;                                      // always 1 more than the last index
                                }
                                RDT.State.NumValuesParsed     += timestampsCount;
                                RDT.State.hitsPerChn[0]       += timestampsCount;
                                RDT.State.NumTotalsEncountered = RDT.State.NumValuesParsed; // only one channel
                            }
                            else                                                            // process
                            {
                                long _max = RDT.State.NumValuesParsed;
                                if (_max < RDT.State.neutronEventArray.Count)                                  // equalize the length of the empty neutron channel event list
                                {
                                    RDT.State.neutronEventArray.RemoveRange((int)_max, RDT.State.neutronEventArray.Count - (int)_max);
                                }
                                else if (_max > RDT.State.neutronEventArray.Count)
                                {
                                    RDT.State.neutronEventArray.AddRange(new uint[_max - RDT.State.neutronEventArray.Count]);
                                }
                                if (_max < RDT.State.timeArray.Count)
                                {
                                    RDT.State.timeArray.RemoveRange((int)_max, RDT.State.timeArray.Count - (int)_max);
                                }
                                else if (_max > RDT.State.timeArray.Count)
                                {
                                    RDT.State.timeArray.AddRange(new ulong[_max - RDT.State.timeArray.Count]);
                                }
                                m_logger.TraceEvent(LogLevels.Verbose, 89, "{0} {1} handling {2} timestampsCount {3} num", elapsed, duration, timestampsCount, RDT.State.NumValuesParsed);
                                RDT.PassBufferToTheCounters((int)_max);
                                maxindex = 0; totalEvents += RDT.State.NumTotalsEncountered;
                                RDT.StartNewBuffer();
                            }
                        }
                    }
                    else
                    {
                        // give the device a break, not needed now because PassBufferToTheCounters processing takes time
                        //Thread.Sleep(40); // 100? ms
                        //m_logger.TraceEvent(LogLevels.Verbose, 99899, "{0} {1} handling {2} timestampsCount {3} num", elapsed, duration, 0, RDT.State.NumValuesParsed);
                    }
                    elapsed = stopwatch.Elapsed;          // snapshot the time after the processing and before the next query

                    if (maxindex > 0)                     // accumulated data was not completely processed above, so it happens here
                    {
                        long _max = RDT.State.NumValuesParsed;
                        if (_max < RDT.State.neutronEventArray.Count)                          // equalize the length of the empty neutron channel event list
                        {
                            RDT.State.neutronEventArray.RemoveRange((int)_max, RDT.State.neutronEventArray.Count - (int)_max);
                        }
                        else if (_max > RDT.State.neutronEventArray.Count)
                        {
                            RDT.State.neutronEventArray.AddRange(new uint[_max - RDT.State.neutronEventArray.Count]);
                        }
                        if (_max < RDT.State.timeArray.Count)
                        {
                            RDT.State.timeArray.RemoveRange((int)_max, RDT.State.timeArray.Count - (int)_max);
                        }
                        else if (_max > RDT.State.timeArray.Count)
                        {
                            RDT.State.timeArray.AddRange(new ulong[_max - RDT.State.timeArray.Count]);
                        }
                        m_logger.TraceEvent(LogLevels.Verbose, 90, "{0} {1} handling {2} num", elapsed, duration, RDT.State.NumValuesParsed);
                        RDT.PassBufferToTheCounters((int)_max);
                        maxindex = 0; totalEvents += RDT.State.NumTotalsEncountered;
                        RDT.StartNewBuffer();
                    }
                }                  // while time elapsed is less than requested time

                stopwatch.Stop();
                m_logger.TraceEvent(LogLevels.Verbose, 11901, "{0} stop time for {1}", DateTime.Now.ToString(), seq);
                m_logger.TraceEvent(LogLevels.Info, 0, "MCA527[{0}]: Finished assay; read {1} events in {2}s for {3}", DeviceName, totalEvents, stopwatch.Elapsed.TotalSeconds, seq);
                m_logger.Flush();
                RDT.Cycle.HighVoltage = m_device.GetHighVoltage();

                if (ps.writingFile)
                {
                    m_device.CreateWriteHeaderAndClose(ps.file);
                    m_logger.TraceEvent(LogLevels.Verbose, 11921, "WriteHeader for {0}", seq);
                    m_logger.Flush();
                }

                lock (m_monitor)
                {
                    m_cancellationTokenSource.Dispose();
                    m_cancellationTokenSource = null;
                }

                if (totalEvents == 0)                  // nothing to handle if no events, close up and continue
                {
                    DAQControl.HandleEndOfCycleProcessing(this, new StreamStatusBlock(@"MCA527 Done"));
                    m_logger.TraceEvent(LogLevels.Verbose, 11911, "HandleEndOfCycle for {0}", seq);
                    m_logger.Flush();
                }
            }
            catch (OperationCanceledException)
            {
                m_logger.TraceEvent(LogLevels.Warning, 767, "MCA527[{0}]: Stopping assay", DeviceName);
                m_logger.Flush();
                DAQControl.StopActiveAssayImmediately();
                throw;
            }
            catch (Exception ex)
            {
                m_logger.TraceEvent(LogLevels.Error, 0, "MCA527[{0}]: Error during assay: {1}", DeviceName, ex.Message);
                m_logger.TraceException(ex, true);
                m_logger.Flush();
                DAQControl.HandleFatalGeneralError(this, ex);
                throw;
            }
            finally
            {
            }
        }
Exemplo n.º 5
0
        /// <summary>
        /// Set up timer callback and action event handlers for a DAQController instance, uses DAQBind subclass
        /// </summary>
        /// <returns></returns>
        public bool InitializeDAQController()
        {
            // DAQ
            daqbind = new DAQBind();
            LMLoggers.LognLM applog = NC.App.Logger(LMLoggers.AppSection.App);


            daqbind.SetEventHandler(ActionEvents.EventType.PreAction, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.PreAction, applog, LogLevels.Verbose, o);
                daqbind.mProgressTracker.ReportProgress(0, s);                //"...");
            });

            daqbind.SetEventHandler(ActionEvents.EventType.ActionPrep, (object o) =>
            {
                string s = "Action Prep: ";
                //can also look at active instrument list here LogLevels.Verbose,
                s = s + Instr.Instruments.Active.Count + " devices found, [" + NC.App.Opstate.SOH + "] " + DateTimeOffset.Now.ToString("MMM dd yyy HH:mm:ss.ff K");
                applog.TraceEvent(LogLevels.Verbose, 0xABCE, "Action Prep: SOH " + o.ToString() + "'");
                daqbind.mProgressTracker.ReportProgress(0, s);                //"Prep");
            });
            daqbind.SetEventHandler(ActionEvents.EventType.ActionStart, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionStart, applog, LogLevels.Verbose, o);
                daqbind.mProgressTracker.ReportProgress(1, s);//"Starting...");
            });

            daqbind.SetEventHandler(ActionEvents.EventType.ActionInProgress, (object o) =>
            {
                measStatus = new MeasurementStatus();
                measStatus.UpdateWithMeasurement();
                measStatus.UpdateWithInstruments();
                updateGUIWithChannelRatesData = true;
                string s2 = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionInProgress, applog, LogLevels.Verbose, o);
                if (!string.IsNullOrEmpty(s2))
                {
                    s2 = "(" + s2 + ")";
                }
                int per = 0;
                try
                {
                    switch (daqbind.CurAction)
                    {
                    case NCCAction.HVCalibration:
                        if (measStatus.snaps.iss != null && measStatus.snaps.iss.hv != null)
                        {
                            per = Math.Abs(Math.Min(100, (int)Math.Round(100.0 * ((double)(measStatus.snaps.iss.hv.HVread - 1) / measStatus.snaps.iss.hv.HVsetpt))));
                            daqbind.mProgressTracker.ReportProgress(per,                             // a % est of steps
                                                                    string.Format("{0} volts, with max voltage {1} {2}", measStatus.snaps.iss.hv.HVread, measStatus.snaps.iss.hv.HVsetpt, s2));
                        }
                        break;

                    default:
                        per = Math.Abs(Math.Min(100, (int)Math.Round(100.0 * ((double)(measStatus.CurrentRepetition - 1) / measStatus.RequestedRepetitions))));
                        daqbind.mProgressTracker.ReportProgress(per,                                                                                                 // a % est of files
                                                                string.Format("{0} of {1} {2}", measStatus.CurrentRepetition, measStatus.RequestedRepetitions, s2)); // dev note: need a better focused description of the state
                        break;
                    }
                }
                catch (ArgumentOutOfRangeException)
                {
                    applog.TraceEvent(LogLevels.Verbose, 58, "{0} inconsistent", per);
                }
            });

            daqbind.SetEventHandler(ActionEvents.EventType.ActionStop, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionStop, applog, LogLevels.Info, o);
                daqbind.mProgressTracker.ReportProgress(100, s);
            });

            daqbind.SetEventHandler(ActionEvents.EventType.ActionCancel, (object o) =>
            {
                string s = DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionCancel, applog, LogLevels.Warning, o);
                daqbind.mProgressTracker.ReportProgress(100, s);
            });

            daqbind.SetEventHandler(ActionEvents.EventType.ActionFinished, (object o) =>
            {
                string s = "";
                if (o != null && o is DAQControl)
                {
                    DAQControl f = (DAQControl)o;
                    measStatus   = new MeasurementStatus(); // preps local state for refresh on channel and computed rates for now, follow up with all other state
                    measStatus.UpdateWithMeasurement();
                    s = DAQControl.MeasStatusString(measStatus);
                    updateGUIWithChannelRatesData = true;
                    UpdateGUIWithNewdata();
                }
                else
                {
                    s = "Finished: SOH " + NC.App.Opstate.SOH + " but no processing occurred  " + DAQControl.LogAndSkimDAQProcessingStatus(ActionEvents.EventType.ActionFinished, applog, LogLevels.Verbose, o);
                }
                NC.App.Opstate.SOH = OperatingState.Stopped;  // in case we got here after a Cancel
                // general logger: to the console, and/or listbox and/or log file or DB
                daqbind.mProgressTracker.ReportProgress(100, s);
                applog.TraceEvent(LogLevels.Verbose, ActionEvents.logid[ActionEvents.EventType.ActionFinished], s);
                // specialized updater for UI or file
                daqbind.mProgressTracker.ReportProgress(100, s);
            });

            return(true);
        }