Explaining how MetroEEG works

Some developers may wish to not include MetroEEG directly but include lower-level code. In this sample we'll endeavor to show a standalone example of getting information from a Mindwave Mobile headset into WP8.

Connecting to a Mindset Mobile Headset
Using the new Bluetooth support in WP8, it's possible to iterate over all paired Bluetooth devices and open a BT-SPP socket to them.
        private StreamSocket socket;
        private async void ConnectToMindset(object sender, RoutedEventArgs e)
        {
            PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";

            var peers = await PeerFinder.FindAllPeersAsync();

            if (!peers.Any(p => p.DisplayName.Contains("MindWave")))
            {
                Print("MindWave not found. Is bluetooth on? Is MindWave paired?");
            }
            else
            {
                PeerInformation spheroPeer = peers.First(p => p.DisplayName.Contains("MindWave"));

                socket = new StreamSocket();
                await socket.ConnectAsync(spheroPeer.HostName, "1");

                Print("Connected to " + peers[0].DisplayName + " bluetooth service.");

                ListenToResult();
            }
First, we get the list of all connected Bluetooth paired devices.
            PeerFinder.AlternateIdentities["Bluetooth:Paired"] = "";

            var peers = await PeerFinder.FindAllPeersAsync();

Next we iterate over that collection attempting to find one with the word "Mindwave" in it. If we don't find one, that could be for multiple reasons: Phone Bluetooth is off, mindwave is off, both devices aren't paired, etc.
            if (!peers.Any(p => p.DisplayName.Contains("MindWave")))
            {
                Print("MindWave not found. Is bluetooth on? Is MindWave paired?");
            }
            else
            {
                PeerInformation spheroPeer = peers.First(p => p.DisplayName.Contains("MindWave"));
            }

And finally we open up a socket that'll be used later to retrieve information from.
                PeerInformation spheroPeer = peers.First(p => p.DisplayName.Contains("MindWave"));

                socket = new StreamSocket();
                await socket.ConnectAsync(spheroPeer.HostName, "1");

                Print("Connected to " + peers[0].DisplayName + " bluetooth service.");

                ListenToResult();
            }

Reading BT-SSP Packet from Mindwave
Mindwave tends to be very chatty and has over 500+ messages every seconds. Those messages are received over as Bytes by C#. We can see the message format at Mindset Communications Protocol.

We'll use socket.InputStream.ReadAsync to read the data sent over by Mindwave.
        private async Task<byte[]> GetNextBuffer(uint length = 512)
        {
            var buffer = await socket.InputStream.ReadAsync(new Windows.Storage.Streams.Buffer(length), length,
                                                            InputStreamOptions.None);

            var resultArray = GetBytesFromBuffer(buffer);
            return resultArray;
        }

        private byte GetByteFromBuffer(IBuffer buffer)
        {
            using (var dr = DataReader.FromBuffer(buffer))
            {
                return dr.ReadByte();
            }
        }

We'll have to continuously read that data in a while (true) loop.
        private async void ListenToResult()
        {
            while (true)
            {
                var resultArray = await GetNextBuffer();
                Debug.WriteLine(string.Join(",", resultArray.Select(b => b.ToString())));
             }
        }

Mindwave sends back A LOT of data. It's quite easy to see a pattern of using 2 bytes of 170, 170 as a separator between message
2012-09-25_15-37-14.png

We're interested in a particular packet that start with 3 bytes of 170,170,32 and has an overall length of 36 bytes. for example:
170,170,32,2,25,131,24,0,0,132,0,0,53,0,0,22,0,0,12,0,0,10,0,0,5,0,0,1,0,0,1,4,0,5,0,84

So, we'll check if the 512 bytes we just read has that 170,170,32 header in it.
        private const int PARSER_SYNC = 0xAA;
        private const int UsefulDataPacketLength = 32;
        private const int LengthOfUsefulPacket =
            /*Syncs Length*/ 2 +
            /* Packet Legnth */ 1 +
            UsefulDataPacketLength +
            /* checksum */ 1;
        private int? GetUsefulDataHeaderIndex(byte[] resultArray)
        {
            for (int i = 0; i < resultArray.Length - 2; i++)
            {
                if (resultArray[i] == PARSER_SYNC
                    && resultArray[i + 1] == PARSER_SYNC
                    && resultArray[i + 2] == UsefulDataPacketLength)
                {
                    return i;
                }
            }
            return null;
        }

If our code has those values then we found the one packet out of 500 packets every second that interested us. Otherwise if that header doesn't exist we can just ignore all 512 bytes.
private async void ListenToResult()
{
    while (true)
    {
        var resultArray = await GetNextBuffer();

        int? indexOfUsefulDataHeader = GetUsefulDataHeaderIndex(resultArray);
        if (indexOfUsefulDataHeader.HasValue == false)
        {
            // ignore data and just dump it
        }
        else
        {

        }
    }
}
One unfortunate thing that might happen is that we get 512 bytes that have the header for our 36 bytes packet, but only a part of it. And we might need to read another 512 bytes to get the full 36 byte packet. Once we have that we'll take the length of the data packet (and 2 more bytes to see the next header).
private async void ListenToResult()
{
    while (true)
    {
        var resultArray = await GetNextBuffer();
        // Debug.WriteLine(string.Join(",", resultArray.Select(b => b.ToString())));

        int? indexOfUsefulDataHeader = GetUsefulDataHeaderIndex(resultArray);
        if (indexOfUsefulDataHeader.HasValue == false)
        {
            // ignore data and just dump it
        }
        else
        {
            // Check if enough data exists to finalize this useful data packet, if not, get another
            if (indexOfUsefulDataHeader.Value  + LengthOfUsefulPacket
                > resultArray.Length)
            {
                var nextResultsArray = await GetNextBuffer();
                resultArray = resultArray.Concat(nextResultsArray).ToArray();
            }

            var usefulDataPacket =
                resultArray
                    .Skip(indexOfUsefulDataHeader.Value)
                    .Take(LengthOfUsefulPacket + 4)
                    .ToArray();

            Debug.Writeline(string.Join(",", usefulDataPacket.Select(b => b.ToString())));
        }
    }
}
When we run this code snippet we can see the following print out with useful data packets.
170,170,32,2,25,131,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,5,0,64,170,170,4,128
170,170,32,2,25,131,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,5,0,64,170,170,4,128
170,170,32,2,25,131,24,5,76,181,1,109,49,0,165,255,0,67,142,0,72,239,0,29,197,0,20,50,0,14,11,4,0,5,0,174,170,170,4,128
170,170,32,2,0,131,24,0,54,21,0,59,230,0,13,123,0,2,35,0,7,190,0,4,121,0,1,134,0,1,18,4,0,5,0,100,170,170,4,128
170,170,32,2,0,131,24,9,131,218,3,187,65,0,25,6,0,188,221,0,190,75,0,61,189,0,9,6,0,39,19,4,0,5,0,240,170,170,4,128
170,170,32,2,0,131,24,16,167,140,0,232,31,0,184,100,0,70,183,0,92,99,0,88,228,0,27,190,0,16,81,4,0,5,0,193,170,170,4,128
170,170,32,2,0,131,24,21,60,117,3,90,220,0,69,112,0,70,240,0,124,10,0,41,106,0,100,249,0,53,199,4,30,5,30,193,170,170,4,128
170,170,32,2,51,131,24,9,47,134,0,37,83,0,6,210,0,12,53,0,6,211,0,15,83,0,4,124,0,2,232,4,30,5,30,246,170,170,4,128
170,170,32,2,25,131,24,5,224,15,3,239,254,2,229,125,0,53,105,0,63,75,0,116,127,0,157,83,0,131,10,4,30,5,30,36,170,170,4,128
170,170,32,2,0,131,24,10,187,109,1,120,31,0,196,103,0,143,57,0,84,87,0,104,26,0,45,53,0,121,65,4,43,5,44,252,170,170,4,128
170,170,32,2,0,131,24,4,47,171,4,68,202,0,55,217,0,128,94,0,37,85,0,94,14,0,36,34,0,59,103,4,43,5,38,92,170,170,4,128
170,170,32,2,0,131,24,5,222,176,0,39,237,0,19,9,0,8,159,0,19,186,0,8,23,0,6,29,0,4,195,4,51,5,44,186,170,170,4,128
170,170,32,2,0,131,24,18,191,112,1,97,97,1,29,246,0,29,133,0,9,8,0,58,54,0,16,23,0,25,175,4,63,5,67,173,170,170,4,128

Parsing Mindwave packets
The mindwave packet is actually pretty easy to parse. Here's a sample packet and it's meaning directly from the docs:
[ 0]: 0xAA // [SYNC]
[ 1]: 0xAA // [SYNC]
[ 2]: 0x20 // [PLENGTH] (payload length) of 32 bytes
[ 3]: 0x02 // [POOR_SIGNAL] Quality
[ 4]: 0x00 // No poor signal detected (0/200)
[ 5]: 0x83 // [ASIC_EEG_POWER_INT]
[ 6]: 0x18 // [VLENGTH] 24 bytes
[ 7]: 0x00 // (1/3) Begin Delta bytes
[ 8]: 0x00 // (2/3)
[ 9]: 0x94 // (3/3) End Delta bytes
[10]: 0x00 // (1/3) Begin Theta bytes
[11]: 0x00 // (2/3)
[12]: 0x42 // (3/3) End Theta bytes
[13]: 0x00 // (1/3) Begin Low-alpha bytes
[14]: 0x00 // (2/3)
[15]: 0x0B // (3/3) End Low-alpha bytes
[16]: 0x00 // (1/3) Begin High-alpha bytes
[17]: 0x00 // (2/3)
[18]: 0x64 // (3/3) End High-alpha bytes
[19]: 0x00 // (1/3) Begin Low-beta bytes
[20]: 0x00 // (2/3)
[21]: 0x4D // (3/3) End Low-beta bytes
[22]: 0x00 // (1/3) Begin High-beta bytes
[23]: 0x00 // (2/3)
[24]: 0x3D // (3/3) End High-beta bytes
[25]: 0x00 // (1/3) Begin Low-gamma bytes
[26]: 0x00 // (2/3)
[27]: 0x07 // (3/3) End Low-gamma bytes
[28]: 0x00 // (1/3) Begin Mid-gamma bytes
[29]: 0x00 // (2/3)
[30]: 0x05 // (3/3) End Mid-gamma bytes
[31]: 0x04 // [ATTENTION] eSense
[32]: 0x0D // eSense Attention level of 13
[33]: 0x05 // [MEDITATION] eSense
[34]: 0x3D // eSense Meditation level of 61
[35]: 0x34 // [CHKSUM] (1's comp inverse of 8-bit Payload sum of 0xCB)

We'll create a new data class that has properties corresponding to each of these items.
   public class MindsetData
    {
        public MindsetData()
        {
        }

        public bool IsQualityOK { get { return Quality == 0; } }

        public MindsetData(int quality, int delta, int theta, int alphaLow, int alphaHigh, int betaLow, int betaHigh, int gammaLow, int gammaMid, int eSenseAttention, int eSenseMeditation, DateTime timestamp)
        {
            Quality = quality;
            Delta = delta;
            Theta = theta;
            AlphaLow = alphaLow;
            AlphaHigh = alphaHigh;
            BetaLow = betaLow;
            BetaHigh = betaHigh;
            GammaLow = gammaLow;
            GammaMid = gammaMid;
            this.eSenseAttention = eSenseAttention;
            this.eSenseMeditation = eSenseMeditation;
            Timestamp = timestamp;
        }

        /// <summary>
        /// Range 0-200. Lower is better. 0 is no quality issues. 
        /// </summary>
        public int Quality { get; private set; }

        /// <summary>
        /// All ranges are Int24.
        /// </summary>
        public int Delta { get; set; }
        public int Theta { get; private set; }
        public int AlphaLow { get; private set; }
        public int AlphaHigh { get; private set; }
        public int BetaLow { get; private set; }
        public int BetaHigh { get; private set; }
        public int GammaLow { get; private set; }
        public int GammaMid { get; private set; }

        /// <summary>
        /// Range 0-100.
        /// </summary>
        public int eSenseAttention { get; private set; }

        /// <summary>
        /// Range 0-100.
        /// </summary>
        public int eSenseMeditation { get; private set; }

        public override string ToString()
        {
            return string.Format(@"Quality: {0}, Delta: {1}, Theta: {2}, AlphaLow: {3}, AlphaHigh: {4}, BetaLow: {5}, BetaHigh: {6}, GammaLow: {7}, GammaMid: {8}, eSenseAttention: {9}, eSenseMeditation: {10}", Quality, Delta, Theta, AlphaLow, AlphaHigh, BetaLow, BetaHigh, GammaLow, GammaMid, eSenseAttention, eSenseMeditation);
        }

        public DateTime Timestamp { get; private set; }
    }

And finally we can unpack the data packet, get the useful data out of it, put it on our data class and print it.
private async void ListenToResult()
{
    while (true)
    {
        var resultArray = await GetNextBuffer();
        //Debug.WriteLine(string.Join(",", resultArray.Select(b => b.ToString())));

        int? indexOfUsefulDataHeader = GetUsefulDataHeaderIndex(resultArray);
        if (indexOfUsefulDataHeader.HasValue == false)
        {
            // ignore data and just dump it
        }
        else
        {
            // Check if enough data exists to finalize this useful data packet, if not, get another
            if (indexOfUsefulDataHeader.Value  + LengthOfUsefulPacket
                > resultArray.Length)
            {
                var nextResultsArray = await GetNextBuffer();
                resultArray = resultArray.Concat(nextResultsArray).ToArray();
            }

            var usefulDataPacket =
                resultArray
                    .Skip(indexOfUsefulDataHeader.Value)
                    .Take(LengthOfUsefulPacket + 4)
                    .ToArray();

            // Debug.WriteLine(string.Join(",", usefulDataPacket.Select(b => b.ToString())));

            // based on http://wearcam.org/ece516/mindset_communications_protocol.pdf
            var data = new MindsetData(
                usefulDataPacket[4],
                GetIntValue(usefulDataPacket, 7, 9),
                GetIntValue(usefulDataPacket, 10, 12),
                GetIntValue(usefulDataPacket, 13, 15),
                GetIntValue(usefulDataPacket, 16, 18),
                GetIntValue(usefulDataPacket, 19, 21),
                GetIntValue(usefulDataPacket, 22, 24),
                GetIntValue(usefulDataPacket, 25, 27),
                GetIntValue(usefulDataPacket, 28, 30),
                usefulDataPacket[32],
                usefulDataPacket[34],
                DateTime.Now);
            Debug.WriteLine(data.ToString());
            //HandleMindsetData(data);
        }
    }
}

The method GetIntValue takes 3 bytes and prints out a corresponding unsigned Int24. Thanks to Bill Reiss that helped me figure out how to convert those bytes to something useful.
    private int GetIntValue(byte[] usefulDataPacket, int beginInclusive, int endInclusive)
    {
        return (usefulDataPacket[beginInclusive] << 16) 
            + (usefulDataPacket[beginInclusive + 1] << 8) 
            + (usefulDataPacket[beginInclusive + 2]);
    }
And finally when we run this app we can see the following print out:
Quality: 25, Delta: 6, Theta: 2, AlphaLow: 0, AlphaHigh: 0, BetaLow: 0, BetaHigh: 0, GammaLow: 0, GammaMid: 0, eSenseAttention: 0, eSenseMeditation: 0
Quality: 25, Delta: 0, Theta: 0, AlphaLow: 0, AlphaHigh: 0, BetaLow: 0, BetaHigh: 0, GammaLow: 0, GammaMid: 0, eSenseAttention: 0, eSenseMeditation: 0
Quality: 25, Delta: 1615213, Theta: 42582, AlphaLow: 31380, AlphaHigh: 46256, BetaLow: 19899, BetaHigh: 7208, GammaLow: 3845, GammaMid: 4395, eSenseAttention: 0, eSenseMeditation: 0
Quality: 0, Delta: 426257, Theta: 33904, AlphaLow: 6628, AlphaHigh: 13725, BetaLow: 7579, BetaHigh: 10067, GammaLow: 8842, GammaMid: 9374, eSenseAttention: 0, eSenseMeditation: 0
Quality: 0, Delta: 583371, Theta: 510441, AlphaLow: 19150, AlphaHigh: 112287, BetaLow: 47943, BetaHigh: 39250, GammaLow: 24035, GammaMid: 17424, eSenseAttention: 0, eSenseMeditation: 0
Quality: 0, Delta: 222602, Theta: 33844, AlphaLow: 23690, AlphaHigh: 22298, BetaLow: 4977, BetaHigh: 15146, GammaLow: 13963, GammaMid: 7088, eSenseAttention: 0, eSenseMeditation: 0
Quality: 0, Delta: 1812838, Theta: 24675, AlphaLow: 39716, AlphaHigh: 23848, BetaLow: 11263, BetaHigh: 6688, GammaLow: 13527, GammaMid: 6301, eSenseAttention: 37, eSenseMeditation: 51
Quality: 0, Delta: 447038, Theta: 305377, AlphaLow: 33535, AlphaHigh: 98314, BetaLow: 99116, BetaHigh: 89947, GammaLow: 19895, GammaMid: 35526, eSenseAttention: 51, eSenseMeditation: 47
Quality: 0, Delta: 46900, Theta: 2625, AlphaLow: 12408, AlphaHigh: 4522, BetaLow: 3427, BetaHigh: 1863, GammaLow: 2832, GammaMid: 1734, eSenseAttention: 51, eSenseMeditation: 63
Quality: 0, Delta: 690112, Theta: 39077, AlphaLow: 3379, AlphaHigh: 4319, BetaLow: 1836, BetaHigh: 3086, GammaLow: 9628, GammaMid: 3239, eSenseAttention: 44, eSenseMeditation: 53
Quality: 0, Delta: 1016230, Theta: 248297, AlphaLow: 117104, AlphaHigh: 24892, BetaLow: 16677, BetaHigh: 30317, GammaLow: 36050, GammaMid: 18515, eSenseAttention: 44, eSenseMeditation: 40
Quality: 0, Delta: 201986, Theta: 18987, AlphaLow: 4782, AlphaHigh: 7936, BetaLow: 3120, BetaHigh: 2211, GammaLow: 2566, GammaMid: 3939, eSenseAttention: 24, eSenseMeditation: 47
Quality: 0, Delta: 26635, Theta: 2578, AlphaLow: 673, AlphaHigh: 1935, BetaLow: 3925, BetaHigh: 4181, GammaLow: 2492, GammaMid: 803, eSenseAttention: 47, eSenseMeditation: 23

Last edited Sep 25, 2012 at 10:54 PM by JustinJosefAngel, version 3

Comments

No comments yet.