using Google.Protobuf; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Media.Imaging; using Tango.BL.Entities; using Tango.Core; using Tango.Core.Helpers; using Tango.Core.IO; using Tango.Logging; using Tango.PMR.Diagnostics; using Tango.Serialization; namespace Tango.Integration.Diagnostics { /// /// Represents a Tango machine diagnostics file recorder capable of recording diagnostics data coming from the machine to a file. /// This file can then be played using the . /// /// /// public class DiagnosticsFileRecorder : ExtendedObject, IDisposable { private ConcurrentQueue _frames; //Holds the queue of data frames ready to be written to file. private Thread _recordingThread; //Holds the recording thread. private TemporaryFile _tempDataFile; //Holds the temporary recording file name. private FileStream _dataFileStream; //Holds the temporary recording file stream. private TaskCompletionSource _stopCompletionSource; //Holds the "Stop" async method completion source. private DiagnosticsTimeCodeChannel _timeCodeChannel; //Holds the diagnostics time code channel. private List _events; private Stopwatch _stopWatch; //Holds the stop watch for keeping tracks over frames time stamps. private DateTime _lastVideoPush; private List _videoFrames; #region Constructors public DiagnosticsFileRecorder() { _lastVideoPush = DateTime.Now; VideoFPS = 10; VideoQuality = 30; } #endregion #region Properties private bool _isRecording; /// /// Gets a value indicating whether the recorder is currently recording. /// public bool IsRecording { get { return _isRecording; } private set { _isRecording = value; RaisePropertyChangedAuto(); } } private long _totalFramesRecorded; /// /// Gets the total frames recorded. /// public long TotalFramesRecorded { get { return _totalFramesRecorded; } private set { _totalFramesRecorded = value; RaisePropertyChanged(nameof(TotalFramesRecorded)); } } private long _totalVideoFramesRecorded; /// /// Gets or sets the total video frames recorded. /// public long TotalVideoFramesRecorded { get { return _totalVideoFramesRecorded; } private set { _totalVideoFramesRecorded = value; RaisePropertyChangedAuto(); } } private int _videoQuality; /// /// Gets or sets the video quality. /// public int VideoQuality { get { return _videoQuality; } set { _videoQuality = value; RaisePropertyChangedAuto(); } } private long _totalBytesDataRecorded; /// /// Gets the total bytes recorded. /// public long TotalDataBytesRecorded { get { return _totalBytesDataRecorded; } private set { _totalBytesDataRecorded = value; RaisePropertyChanged(nameof(TotalDataBytesRecorded)); } } private long _totalBytesRecorded; /// /// Gets or sets the total bytes recorded. /// public long TotalBytesRecorded { get { return _totalBytesRecorded; } set { _totalBytesRecorded = value; RaisePropertyChangedAuto(); } } /// /// Gets the recording time. /// public TimeSpan RecordingTime { get { return _stopWatch != null ? _stopWatch.Elapsed : TimeSpan.Zero; } } /// /// Gets or sets the video recording frame rate. /// public int VideoFPS { get; set; } #endregion #region Public Methods /// /// Start recording. The actual recording will start only on the next call. /// public void Start() { if (!IsRecording) { if (_stopWatch != null) { _stopWatch.Stop(); _stopWatch = null; } _tempDataFile = TemporaryManager.CreateFile(); _frames = new ConcurrentQueue(); _events = new List(); _timeCodeChannel = new DiagnosticsTimeCodeChannel(); TotalDataBytesRecorded = 0; TotalFramesRecorded = 0; TotalVideoFramesRecorded = 0; TotalBytesRecorded = 0; _dataFileStream = _tempDataFile.CreateStream(); IsRecording = true; _recordingThread = new Thread(RecordingThreadMethod); _recordingThread.IsBackground = true; _recordingThread.Start(); } } /// /// Stop recording. /// /// public Task Stop() { if (IsRecording) { IsRecording = false; _stopCompletionSource = new TaskCompletionSource(); return _stopCompletionSource.Task; } else { return Task.FromResult(new object()); } } /// /// Writes a machine event to the diagnostics file. /// /// The data. public void Write(MachinesEvent ev) { _events.Add(new DiagnosticsFileEvent(ev)); } /// /// Writes a diagnostics packet to the recording. /// /// The data. public void Write(StartDiagnosticsResponse data) { DataFileFrame frame = new DataFileFrame(); frame.StartDiagnosticsResponse = data; Write(frame); } /// /// Writes the specified video frames. Each video frame is a different camera! /// /// The video frames. public void Write(IEnumerable videoFrames) { if (!IsRecording) throw LogManager.Log(new InvalidOperationException("Please start the recorder before attempting to push data.")); int milli = 1000 / VideoFPS; if (DateTime.Now >= _lastVideoPush.AddMilliseconds(milli)) { _videoFrames = videoFrames.ToList(); _lastVideoPush = DateTime.Now; } } /// /// Writes the specified data frame to the recording. /// /// The frame. /// Please start the recorder before attempting to push data. public void Write(DataFileFrame frame) { if (!IsRecording) throw LogManager.Log(new InvalidOperationException("Please start the recorder before attempting to push data.")); if (_stopWatch == null) { _stopWatch = new Stopwatch(); _stopWatch.Start(); } frame.Milliseconds = (int)_stopWatch.Elapsed.TotalMilliseconds; _frames.Enqueue(frame); RaisePropertyChanged(nameof(RecordingTime)); } /// /// Saves the current recording to the specified file. (Use only after calling "Stop"). /// /// Name of the file. /// /// /// Cannot save a recording file while recording. /// or /// No data was captured during recording time. Cannot save recording /// public Task Save(String fileName) { if (IsRecording) { throw LogManager.Log(new InvalidOperationException("Cannot save a recording file while recording.")); } if (_timeCodeChannel.Frames.Count == 0) { throw LogManager.Log(new InvalidOperationException("No data was captured during recording time. Cannot save recording")); } return Task.Factory.StartNew(() => { BinaryDataSerializer serializer = new BinaryDataSerializer(); _timeCodeChannel.Events = _events; byte[] timeCodeData = serializer.SerializeToBytes(_timeCodeChannel); using (FileStream fs = new FileStream(fileName, FileMode.Create)) { BinaryWriter writer = new BinaryWriter(fs); writer.Write(timeCodeData.Length); writer.Write(timeCodeData, 0, timeCodeData.Length); using (Stream dataFileStream = _tempDataFile.CreateStream()) { dataFileStream.CopyTo(fs); } } }); } #endregion #region Recording Thread /// /// Handles the recording thread. /// private void RecordingThreadMethod() { while (IsRecording) { while (_frames.Count > 0) { DataFileFrame frame = null; if (_frames.TryDequeue(out frame)) { DiagnosticsTimeCodeChannelFrame timeCodeFrame = new DiagnosticsTimeCodeChannelFrame(); timeCodeFrame.Position = TotalDataBytesRecorded; if (_videoFrames != null && _videoFrames.Count > 0) { foreach (var videoFrame in _videoFrames) { if (videoFrame != null) { var bytes = ByteString.CopyFrom(videoFrame.ToJpegBytes(VideoQuality)); frame.VideoFrames.Add(bytes); TotalBytesRecorded += bytes.Length; } } _videoFrames = null; TotalVideoFramesRecorded++; } byte[] data = frame.ToBytes(); _dataFileStream.Write(data, 0, data.Length); TotalFramesRecorded++; TotalDataBytesRecorded += data.Length; TotalBytesRecorded += data.Length; timeCodeFrame.Milliseconds = frame.Milliseconds; timeCodeFrame.FrameLength = data.Length; _timeCodeChannel.Frames.Add(timeCodeFrame); } } Thread.Sleep(10); } _dataFileStream.Dispose(); if (_stopCompletionSource != null) { _stopCompletionSource.SetResult(new object()); } } #endregion #region IDisposable /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { _tempDataFile.Delete(); } #endregion } }