// Class method used to Start a Real-Time acquisition through Plux::Source configuration: // samplingRate -> Desired sampling rate that will be used during the data acquisition stage. // The used units are in Hz (samples/s) // sourcesArray -> List of Sources that define which channels are active and its internal configurations, // namely the resolution and frequency divisor. public void StartAcquisitionBySourcesUnity(int samplingRate, PluxSource[] sourcesArray) { // Reboot BufferedSamples object. BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { bufferedSamples.reinitialise(); } if (!bufferedSamples.getUncaughtExceptionState()) { // >>> Garbage collector memory management. GCHandle pinnedArray = GCHandle.Alloc(sourcesArray, GCHandleType.Pinned); // >>> Convert to a memory address. IntPtr ptr = pinnedArray.AddrOfPinnedObject(); // >>> Call correspondent .dll method to start the real-time acquisition. StartAcquisitionBySources(samplingRate, ptr, sourcesArray.Length); // >>> Releasing memory. pinnedArray.Free(); // Start Communication Loop. StartLoopUnity(); } else { throw new Exception("Unable to start a real-time acquisition. It is probable that the connection between the computer and the PLUX device was broke"); } // Update global flag. AcquisitionStopped = false; }
// Auxiliary function that manages the scanning process. // domains -> Array of strings that defines which domains will be used while searching for PLUX devices // [Valid Options: "BTH" -> classic Bluetooth; "BLE" -> Bluetooth Low Energy; "USB" -> Through USB connection cable] private void ScanPluxDevs(List <string> domains) { try { // Search for BLE and BTH devices. List <string> listDevices = new List <string>(); List <String> devicesFound = PluxDevsFound.Value; // Clear previous content of the device list. devicesFound.Clear(); for (int domainNbr = 0; domainNbr < domains.Count; domainNbr++) { // List of available Devices. GetDetectableDevices(domains[domainNbr]); } // Send data (list of devices found) to the MAIN THREAD. UnityThreadHelper.Dispatcher.Dispatch(() => ScanResultsCallback(devicesFound)); } catch (ExecutionEngineException exc) { Debug.Log("Exception found while scanning: \n" + exc.Message + "\n" + exc.StackTrace); BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { bufferedSamples.actUncaughtException(); } } }
/** * Auxiliary method used to reboot the buffer responsible for storing the packages of collected data. */ public void RebootDataBuffer() { // Clear the buffer containing the packages of collected data. // Lock is an essential step to ensure that variables shared by the same thread will not be accessed at the same time. BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { bufferedSamples.reboot(); } }
// Callback Handler (function invoked during signal acquisition, being essential to ensure the // communication between our C++ API and the Unity project. bool CallbackHandler(int nSeq, int[] data, int dataLength) { // Lock is an essential step to ensure that variables shared by the same thread will not be accessed at the same time. BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { // Storage of the received data samples. // Samples are organized in a sequential way, so if channels 1 and 4 are active it means that // data[0] will contain the sample value of channel 1 while data[1] is the sample collected on channel 4. bufferedSamples.addSamples(nSeq, data); } return(true); }
// Method used to check if any unhandled exception was raised until the moment. public bool IsExceptionInBuffer() { // Lock is an essential step to ensure that variables shared by the same thread will not be accessed at the same time. BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { if (bufferedSamples.getUncaughtExceptionState()) { bufferedSamples.deactUncaughtException(); throw new ExternalException("An exception with unknown origin was raised, but it is not fatal. It is probable that the device connection was lost..."); } return(false); } }
// Getter method used to request a new package of data. // channelNbr -> Number of the channel under analysis. // activeChannelsMask -> List containing set of active channels. // rebootMemory -> When true the stored data inside BufferedSamples object is re-initialized. public int[] GetPackageOfData(int channelNbr, List <int> activeChannelsMask, bool rebootMemory) { // Lock is an essential step to ensure that variables shared by the same thread will not be accessed at the same time. BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { if (!bufferedSamples.getUncaughtExceptionState()) { // Identification of the current channel index. int auxCounter = activeChannelsMask.IndexOf(channelNbr); // Initialisation of local variables. int[][] fullData = bufferedSamples.getPackages(rebootMemory); // Selection of data inside the desired channel. if (fullData != null && fullData[0] != null) { int[] dataInChannel = new int[fullData.Length]; for (int i = 0; i < dataInChannel.Length; i++) { try { dataInChannel[i] = fullData[i][auxCounter]; } catch (Exception exc) { bufferedSamples.reboot(); DoubleCheckLock = null; return(new int[0]); } } return(dataInChannel); } return(new int[0]); } else { bufferedSamples.deactUncaughtException(); throw new ExternalException("An exception with unknown origin was raised, but it is not fatal. It is probable that the device connection was lost..."); } } }
// Getter method used to request a new package of data. // rebootMemory -> When true the stored data inside BufferedSamples object is re-initialized. public int[][] GetPackageOfData(bool rebootMemory) { // Lock is an essential step to ensure that variables shared by the same thread will not be accessed at the same time. BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { if (!bufferedSamples.getUncaughtExceptionState()) { return(bufferedSamples.getPackages(rebootMemory)); } else { bufferedSamples.deactUncaughtException(); throw new ExternalException("An exception with unknown origin was raised, but it is not fatal. It is probable that the device connection was lost..."); } } }
// Class method used to Start a Real-Time acquisition: // samplingRate -> Desired sampling rate that will be used during the data acquisition stage. // The used units are in Hz (samples/s) // listChannels -> A list where there are specified the active channels. Each entry contains a port number of an active channel. // resolution -> Analog-to-Digital Converter (ADC) resolution. This parameter defines how precise are the digital sampled values when // compared with the ideal real case scenario. // callbackFunction -> Pointer to the callback function that will be used to send/communicate the data acquired by PLUX devices, i.e., this callback // function will be invoked during the data acquisition process and through his inputs the acquired data will become accessible. // So, for receiving data on a third-party application it will only be necessary to redefine this callbackFunction. public void StartAcquisitionUnity(int samplingRate, List <int> listChannels, int resolution) { // Conversion of List of active channels to a string format. for (int i = 0; i < 11; i++) { if (listChannels.Contains(i + 1)) { ActiveChannelsStr += "1"; } else { ActiveChannelsStr += "0"; } } // Reboot BufferedSamples object. BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { bufferedSamples.reinitialise(); } if (!bufferedSamples.getUncaughtExceptionState()) { // Start of acquisition. StartAcquisition(samplingRate, ActiveChannelsStr, resolution); // Start Communication Loop. StartLoopUnity(); } else { throw new Exception("Unable to start a real-time acquisition. It is probable that the connection between the computer and the PLUX device was broke"); } // Update global flag. AcquisitionStopped = false; }
// Factory for our Multi-Thread lazy object. static BufferAcqSamples InitBufferedSamplesObject() { BufferAcqSamples lazyComponent = new BufferAcqSamples(); return(lazyComponent); }
// Callback function responsible for receiving the acquired data samples from the communication loop started by StartLoopUnity(). private bool DllCommunicationHandler(int exceptionCode, string exceptionDescription, int nSeq, IntPtr data, int dataInSize) { lock (callbackPointer) { try { // DEBUG //Console.WriteLine("Monitoring: " + nSeq + "|" + data + "|" + dataInSize); // Check if no exception were raised. if (exceptionCode == 0) { try { // Convert our data pointer to an array format. int[] dataArray = new int[dataInSize]; Marshal.Copy(data, dataArray, 0, dataInSize); callbackPointer.GetCallbackRef()(nSeq, dataArray, dataInSize); } catch (OutOfMemoryException exception) { BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { bufferedSamples.actUncaughtException(); Debug.Log("Executing preventive approaches to deal with a potential OutOfMemoryException:\n" + exception.StackTrace); } } } else if (exceptionCode == 777) // Receiving devices found during scan. { // Store list of found devices in a global variable shared between threads. Debug.Log("Receiving Device: " + exceptionDescription); List <String> devicesFound = PluxDevsFound.Value; devicesFound.Add(exceptionDescription); } else { Debug.Log("A new C++ exception was found..."); // Check if the current exception could be an uncaught one. BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { // Activate flag in BufferedSamples object stating that an uncaught exception exists. bufferedSamples.actUncaughtException(); } throw new Exception(exceptionCode.ToString() + " | " + exceptionDescription); } } catch (OutOfMemoryException exception) { BufferAcqSamples bufferedSamples = LazyObject.Value; lock (bufferedSamples) { bufferedSamples.actUncaughtException(); Debug.Log("Executing preventive approaches to deal with a potential OutOfMemoryException:\n" + exception.StackTrace); } } return(true); } }