Search code examples
c#unity-game-engineunityscript

Unity with Neurosky - Port scanning


I have this C# script on Unity to scan available serial ports to connect to Neurosky. However, this is a manual detection, this just works on computers whose Starting port of ThinkGear Connector is COM9.

void setupNeuro() {
    tgHandleId = ThinkGear.TG_GetNewConnectionId();
    tgConnectionStatus = ThinkGear.TG_Connect(tgHandleId,
                                              "\\\\.\\COM9",
                                              ThinkGear.BAUD_9600,
                                              ThinkGear.STREAM_PACKETS);
    }

How to edit this C# script to automatically detect a right port from COM1 to COMxx ?


Solution

  • This isn't a Unity problem as much as it is a C# one. The ThinkGear docs mention that users should implement port scanning, but I don't recall there being any implementation provided, although the suggestion of storing the previous port is provided.

    Unfortunately, there are no truly elegant ways to implement this, but there are ways.

    The best you can do is looping through the ports until you get one that doesn't timeout, but this means each check needs to take at least 2 seconds. And to make matters worse, the only method you have to get connected Serial Ports from .NET in Unity isn't guaranteed to be up to date either. This means you might end up enumerating over a ton of serial ports in a really slow manner.

    To minimize search times you should search in this order:

    • Last port that was used (Store this in PlayerPrefs)

    • All ports returned by SerialPort.GetPortNames. There won't be many, but unfortunately, there's no guarantee they all exist, since, as the docs say, SerialPort.GetPortNames checks a registry value that is not always up to date.

    • Ports 0-10 if you haven't already checked them.

    • Ports 10 - 256, but see below. At this point you'll have to at least give the user a chance to enter the port themselves, or give them a warning about how long the next step will take.

      I wouldn't recommend going this far (does up to 8 minutes of searching sound reasonable?). You'll already have spent up to 20 seconds scanning the first 10 ports. It might be worth it to

      • Show users how to find the right port themselves
      • Write a small external program for each platform that uses lower level methods to display the right port for the user to enter.
      • Access those lower level methods from a OS-specific library and access it from Unity to limit your search to valid ports. This is the choice I'd go with.

    Checking a port goes something like this (the lambda is needed because of the use of a coroutine):

    IEnumerable AttemptHeadsetConnection(int portNumber,Action<int,int> headsetConnectedCallback, Action attemptCompletedCallback)
    {
        var connectionString = string.Format("\\\\.\\COM{0}",portNumber);//That string literal should be elsewhere
        return AttemptHeadsetConnection(connectionString, headsetConnectedCallback, attemptCompletedCallback);
    }
    
    IEnumerable AttemptHeadsetConnection(string connectionString,Action<int,int> headsetConnectedCallback,Action attemptCompletedCallback)
    {
        connectionID = ThinkGear.TG_GetNewConnectionId();
        connectionStatus = ThinkGear.TG_Connect(connectionID ,
                                              connectionString,
                                              ThinkGear.BAUD_9600,
                                              ThinkGear.STREAM_PACKETS);
        if(connectStatus >= 0)
        {
           yield return new WaitForSeconds(2f); //Give the headset at least 2 seconds to respond with valid data
           int receivedPackets = ThinkGear.TG_ReadPackets(handleID, -1);//Read all the packets with -1
           if(receivedPackets > 0)
           {
               headsetConnectedCallback(connectionID,connectionStatus);
           } 
           else 
           {
              ThinkGear.TG_FreeConnection(handleID);              
           }
    
       }
       attemptCompletedCallback();
    }
    

    And use that with something like:

    foreach(var serialPort in SerialPort.GetPortNames())
    {
         var connectionCoroutine = AttemptHeadsetConnection(serialPort,onFoundHeadset,onAttemptCompleted);
         StartCoroutine(connectionCoroutine);
    }
    

    Note about the code: It's not elegant, and it might not even compile (although it doesn't do anything that's impossible). Take it as very convincing psuedo-code and use it as your base.