/// <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; } }
/// <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 { } }