Exception thrown when connecting to MetaMotionR device

edited September 2019 in C#

Hello,

I am currently trying to integrate MetaMotionR device support into our application. I have two MetaMotionR devices. Using the C# tutorials, I managed to connect and retrieve accelerometer data and battery status from them in our application. The problem I have is that sometimes when I try to connect to one of the devices, while connected to the other one, I receive the following exception:

System.Transactions Critical: 0 : <TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Critical"><TraceIdentifier>http://msdn.microsoft.com/TraceCodes/System/ActivityTracing/2004/07/Reliability/Exception/Unhandled</TraceIdentifier><Description>Unhandled exception</Description><AppDomain>Guideline 5.exe</AppDomain><Exception><ExceptionType>System.InvalidOperationException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>Collection was modified after the enumerator was instantiated.</Message><StackTrace>   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Queue`1.Enumerator.MoveNext()
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at MbientLab.MetaWear.Impl.MetaWearBoard.ModuleBoardBridge.&amp;lt;writeValue&amp;gt;d__9.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()</StackTrace><ExceptionString>System.InvalidOperationException: Collection was modified after the enumerator was instantiated.
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.Queue`1.Enumerator.MoveNext()
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
   at MbientLab.MetaWear.Impl.MetaWearBoard.ModuleBoardBridge.&amp;lt;writeValue&amp;gt;d__9.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()</ExceptionString></Exception></TraceRecord>
An unhandled exception of type 'System.InvalidOperationException' occurred in mscorlib.dll Collection was modified after the enumerator was instantiated.

The procedure to close the connection to the previous one and connect to the other one is the following:

  1. Stop the accelerometer of the currently connected sensor using:

    public void Stop(IMetaWearBoard device)
    {
        if (device == null)
        {
            throw new ArgumentNullException("The provided device is null!");
        }
    
        // Mark the sensor as inactive
        SensorEnabled = false;
        var acc = device.GetModule<IAccelerometer>();
        // Put accelerometer back into standby mode
        acc.Stop();
        // Stop accelerationi data
        acc.Acceleration.Stop();
    }
    
  2. Disconnect using:

    public async virtual Task Disconnect()
    {
        if (_metawearDevice == null)
        {
            throw new ArgumentNullException("The MetaWear device instance is null!");
        }
    
        MBientDeviceConnected = false;
        _metawearDevice.TearDown();
    
        // Have the board terminate the BLE connection
        await _metawearDevice.GetModule<IDebug>().DisconnectAsync();
    }
    
  3. Connect to the new device using:

    public async virtual Task<IMetaWearBoard> Connect(BluetoothLEDevice device, int retries = 1)
    {
        if (device == null)
        {
            throw new ArgumentNullException("The provided Bluetooth device is null!");
        }
    
        _metawearDevice = Application.GetMetaWearBoard(device);
        // How long the API should wait (in milliseconds) before a required response is received
        _metawearDevice.TimeForResponse = TimeForResponse;
        _log.Info($"Connecting to {_metawearDevice.MacAddress}...");
    
        do
        {
            try
            {
                await _metawearDevice.InitializeAsync();
                retries = -1;
            }
            catch (Exception e)
            {
                _log.Error($"Error connecting to {_metawearDevice.MacAddress}, retrying...");
                retries--;
            }
        } while (retries > 0);
    
        if (retries == 0)
        {
            MBientDeviceConnected = false;
            return null;
        }
        else
        {
            _log.Info($"Connected to {_metawearDevice.MacAddress}");
            MBientDeviceConnected = true;
            return _metawearDevice;
        }
    }
    
  4. Shortly after the line "_log.Info($"Connected to {_metawearDevice.MacAddress}");" from the "Connect" method (step 3) is executed, the exception mentioned above appears.

My guess is that there are some problems with the connect and disconnect procedure, since sometimes I have to try to connect twice in order to receive accelerometer data, even if the "Connect" method from step 3 returns success every time. The BLE connection interval is set to 7.5ms, the accelerometer's data rate is 100 and the data range is 16.

Can you point me to the direction of the problem? I can give any extra information you need.

Thank you,
Florin

Comments

  • Hello,

    I'm sorry for my negligence.

    1. Board Information:
      Hardware revision: 0.4
      Firmware revision: 1.4.4
      Model number: 5

    2. Host Device Information: Laptop with Windows 10 x64, version 1809.

    3. SDK: I am using Visual Studio 2017, version 15.9.14

    Thank you,
    Florin

  • I don't see anything obviously wrong in your code snippets.

    The stack trace points to this function but I also don't see how it would be throwing an InvalidOperationException.

    Please provide a self contained piece of metawear SDK calls that replicates the issue We should able to copy/paste the code and run it ourselves with minimal effort.

  • The code to reproduce the problem is the following. What you need is to add objects of type "BluetoothLEDevice" into the list "DeviceList" or to replace the list with a single element. I used the code from the BtleDeviceScanner project from the MetaWear tutorials to create BluetoothLEDevice objects.

    What you need to do to recreate the problem is to call the "Connect" method several times consecutively. I found that this exception does not occur in a regular manner.

    using MbientLab.MetaWear;
    using MbientLab.MetaWear.Core;
    using MbientLab.MetaWear.Data;
    using MbientLab.MetaWear.Sensor;
    using MbientLab.MetaWear.Win10;
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using Windows.Devices.Bluetooth;
    
    private List<BluetoothLEDevice> DeviceList = new List<BluetoothLEDevice>();               
    public List<double> GetX { get; } = new List<double>();
    public List<double> GetY { get; } = new List<double>();
    public List<double> GetZ { get; } = new List<double>();
    IMetaWearBoard _metawearDevice = null;
    
    // MAC of the desired BLE device, format "xx:xx:xx:xx:xx:xx"
    public void Connect(string MAC, int retries = 1)
    {
        if (_metawearDevice != null)
        {
            StopAndDisconnectMotionSensor();
        }
        ConnectToSensor(MAC.Trim(), retries);
    }
    
    public void StopAndDisconnectMotionSensor()
    {
        StopAccelerometer(_metawearDevice);
        Task task = Task.Run(async () => await Disconnect(_metawearDevice));
    }
    
    public void StopAccelerometer(IMetaWearBoard _metawearDevice)
    {
        if (_metawearDevice == null)
        {
            throw new ArgumentNullException("The provided device is null!");
        }
    
        var acc = _metawearDevice.GetModule<IAccelerometer>();
        // Put accelerometer back into standby mode
        acc.Stop();
        // Stop accelerationi data
        acc.Acceleration.Stop();
    }
    
    public async virtual Task Disconnect(IMetaWearBoard _metawearDevice)
    {
        if (_metawearDevice == null)
        {
            throw new ArgumentNullException("The MetaWear device instance is null!");
        }
    
        _metawearDevice.TearDown();
        // Have the board terminate the BLE connection
        await _metawearDevice.GetModule<IDebug>().DisconnectAsync();
    }
    
    public void ConnectToSensor(string MAC, int retries = 3)
    {
        BluetoothLEDevice device = DeviceList.Find(x => ConvertToMAC(x.BluetoothAddress).Trim() == MAC.Trim());
        Task task = Task.Run(async () => await AttemptConnect(device, retries));
    }
    
    private async Task AttemptConnect(BluetoothLEDevice BLEDevice, int retries)
    {
        _metawearDevice = await ConnectToDevice(BLEDevice, retries);
    
        if (_metawearDevice != null)
        {
            Task task = Task.Run(async () => await Setup(_metawearDevice));
            SetAccSamplingRate(_metawearDevice, 100f, 16f);
            StartAcc(_metawearDevice);
        }
    }
    
    public async Task Setup(IMetaWearBoard device, float connInterval = 7.5f)
    {
        if (device == null)
        {
            throw new ArgumentNullException("The provided device is null!");
        }
    
        // Set the connection interval
        await SetBLEConnectionInterval(device, connInterval);
        var acc = device.GetModule<IAccelerometer>();
    
        // Use data route framework to tell the MetaMotion to stream accelerometer data to the host device
        await acc.Acceleration.AddRouteAsync(source => source.Stream(data =>
        {
            // Clear buffers if there is too much data inside them
            if (GetX.Count > 1000)
            {
                ClearSensorData();
            }
    
            // Buffer received data
            GetX.Add(data.Value<Acceleration>().X);
            GetY.Add(data.Value<Acceleration>().Y);
            GetZ.Add(data.Value<Acceleration>().Z);
        }));
    }
    
    public void ClearSensorData()
    {
        GetX.Clear();
        GetY.Clear();
        GetZ.Clear();
    }
    
    private async Task SetBLEConnectionInterval(IMetaWearBoard device, float maxConnInterval = 7.5f)
    {
        if (device == null)
        {
            throw new ArgumentNullException("The provided device is null!");
        }
    
        // Adjust the max connection interval
        device.GetModule<ISettings>()?.EditBleConnParams(maxConnInterval: maxConnInterval);
        await Task.Delay(1500);
    }
    
    public void SetAccSamplingRate(IMetaWearBoard device, float samplingRate = 100f, float dataRange = 16f)
    {
        if (device == null)
        {
            throw new ArgumentNullException("The provided device is null!");
        }
    
        var acc = device.GetModule<IAccelerometer>();
        // Set the data rate and data to the specified values or closest valid values
        acc.Configure(odr: samplingRate, range: dataRange);
    }
    
    public void StartAcc(IMetaWearBoard device)
    {
        if (device == null)
        {
            throw new ArgumentNullException("The provided device is null!");
        }
    
        var acc = device.GetModule<MbientLab.MetaWear.Sensor.IAccelerometer>();
        // Start the acceleration data
        acc.Acceleration.Start();
        // Put accelerometer in active mode
        acc.Start();
    }
    
    public async virtual Task<IMetaWearBoard> ConnectToDevice(BluetoothLEDevice device, int retries = 1)
    {
        _metawearDevice = Application.GetMetaWearBoard(device);
    
        if (_metawearDevice == null)
        {
            throw new ArgumentNullException("The MetaWear device is null!");
        }
    
        // How long the API should wait (in milliseconds) before a required response is received
        _metawearDevice.TimeForResponse = 5000;
        int x = retries;
    
        do
        {
            try
            {
                await _metawearDevice.InitializeAsync();
                retries = -1;
            }
            catch (Exception e)
            {
                retries--;
            }
        } while (retries > 0);
    
        if (retries == 0)
        {
            return null;
        }
        else
        {
            return _metawearDevice;
        }
    }
    

    Thank you,
    Florin

  • Well, there's your problem, you need to wait for async tasks to complete.

  • @Eric said:
    Well, there's your problem, you need to wait for async tasks to complete.

    Can you please tell me where in the code above do I need to wait for async tasks to complete?

    Thank you,
    Florin

Sign In or Register to comment.