thinkgear_communications_protocol.txt · Last modified: 2017/01/17 16:22 by masasomeha

ThinkGear Serial Stream Guide

Introduction

ThinkGear™ is the technology inside every NeuroSky product or partner product that enables a device to interface with the wearers’ brainwaves. It includes the sensor that touches the forehead, the contact and reference points located on the ear pad, and the onboard chip that processes all of the data and provides this data to software and applications in digital form. Both the raw brainwaves and the eSense Meters (Attention and Meditation) are calculated on the ThinkGear chip.

The ThinkGear Serial Stream SDK document (formerly referred to as the ThinkGear Communications Protocol) defines, in detail, how to communicate with the ThinkGear modules. In particular, it describes:

  • How to parse the serial data stream of bytes to reconstruct the various types of brainwave data sent by the ThinkGear
  • How to interpret and use the various types of brainwave data that are sent from the ThinkGear (including Attention, Meditation, and signal quality data) in a BCI application
  • How to send reconfiguration Command Bytes to the ThinkGear, for on-the-fly customization of the module's behavior and output

The ThinkGear Data Values chapter defines the types of Data Values that can be reported by ThinkGear. It is highly recommended that you read this section to familiarize yourself with which kinds of Data Values are (and aren't) available from ThinkGear before continuing to later chapters.

The ThinkGear Packets chapter describes the ThinkGear Packet format used to deliver the ThinkGear Data Values.

The ThinkGear Command Bytes chapter is for advanced users and covers how to send Command Bytes to the ThinkGear in order to customize its configuration (change baud rate, enable/disable certain Data Value outputs, etc).

ThinkGear Data Values

POOR_SIGNAL Quality

This unsigned one-byte integer value describes how poor the signal measured by the ThinkGear is. It ranges in value from 0 to 255. Any non-zero value indicates that some sort of noise contamination is detected. The higher the number, the more noise is detected. A value of 200 has a special meaning, specifically that the ThinkGear electrodes aren't contacting a person's skin.

This value is typically output every second, and indicates the poorness of the most recent measurements.

Poor signal may be caused by a number of different things. In order of severity, they are:

  • Sensor, ground, or reference electrodes not being on a person's head (i.e. when nobody is wearing the ThinkGear).
  • Poor contact of the sensor, ground, or reference electrodes to a person's skin (i.e. hair in the way, or headset which does not properly fit a person's head, or headset not properly placed on the head).
  • Excessive motion of the wearer (i.e. moving head or body excessively, jostling the headset).
  • Excessive environmental electrostatic noise (some environments have strong electric signals or static electricity buildup in the person wearing the sensor).
  • Excessive non-EEG biometric noise (i.e. EMG, EKG/ECG, EOG, etc)

A certain amount of noise is unavoidable in normal usage of ThinkGear, and both NeuroSky's filtering technology and eSense™ algorithm have been designed to detect, correct, compensate for, account for, and tolerate many types of non-EEG noise. Most typical users who are only interested in using the eSense values, such as Attention and Meditation, do not need to worry too much about the POOR_SIGNAL Quality value, except to note that the Attention and Meditation values will not be updated while POOR_SIGNAL is detected. The POOR_SIGNAL Quality value is more useful to some applications which need to be more sensitive to noise (such as some medical or research applications), or applications which need to know right away when there is even minor noise detected.

By default, output of this Data Value is enabled. It is typically output once a second.

eSense(tm) Meters

For all the different types of eSenses (i.e. Attention, Meditation), the meter value is reported on a relative eSense scale of 1 to 100. On this scale, a value between 40 to 60 at any given moment in time is considered “neutral”, and is similar in notion to “baselines” that are established in conventional EEG measurement techniques (though the method for determining a ThinkGear baseline is proprietary and may differ from conventional EEG). A value from 60 to 80 is considered “slightly elevated”, and may be interpreted as levels being possibly higher than normal (levels of Attention or Meditation that may be higher than normal for a given person). Values from 80 to 100 are considered “elevated”, meaning they are strongly indicative of heightened levels of that eSense.

Similarly, on the other end of the scale, a value between 20 to 40 indicates “reduced” levels of the eSense, while a value between 1 to 20 indicates “strongly lowered” levels of the eSense. These levels may indicate states of distraction, agitation, or abnormality, according to the opposite of each eSense.

An eSense meter value of 0 is a special value indicating the ThinkGear is unable to calculate an eSense level with a reasonable amount of reliability. This may be (and usually is) due to excessive noise as described in the POOR_SIGNAL Quality section above.

The reason for the somewhat wide ranges for each interpretation is that some parts of the eSense algorithm are dynamically learning, and at times employ some “slow-adaptive” algorithms to adjust to natural fluctuations and trends of each user, accounting for and compensating for the fact that EEG in the human brain is subject to normal ranges of variance and fluctuation. This is part of the reason why ThinkGear sensors are able to operate on a wide range of individuals under an extremely wide range of personal and environmental conditions while still giving good accuracy and reliability. Developers are encouraged to further interpret and adapt these guideline ranges to be fine-tuned for their application (as one example, an application could disregard values below 60 and only react to values between 60-100, interpreting them as the onset of heightened attention levels).

ATTENTION eSense

This unsigned one-byte value reports the current eSense Attention meter of the user, which indicates the intensity of a user's level of mental “focus” or “attention”, such as that which occurs during intense concentration and directed (but stable) mental activity. Its value ranges from 0 to 100. Distractions, wandering thoughts, lack of focus, or anxiety may lower the Attention meter levels. See eSense(tm) Meters above for details about interpreting eSense levels in general.

By default, output of this Data Value is enabled. It is typically output once a second.

MEDITATION eSense

This unsigned one-byte value reports the current eSense Meditation meter of the user, which indicates the level of a user's mental “calmness” or “relaxation”. Its value ranges from 0 to 100. Note that Meditation is a measure of a person's mental levels, not physical levels, so simply relaxing all the muscles of the body may not immediately result in a heightened Meditation level. However, for most people in most normal circumstances, relaxing the body often helps the mind to relax as well. Meditation is related to reduced activity by the active mental processes in the brain, and it has long been an observed effect that closing one's eyes turns off the mental activities which process images from the eyes, so closing the eyes is often an effective method for increasing the Meditation meter level. Distractions, wandering thoughts, anxiety, agitation, and sensory stimuli may lower the Meditation meter levels. See “eSense Meters” above for details about interpreting eSense levels in general.

By default, output of this Data Value is enabled. It is typically output once a second.

8BIT_RAW Wave Value

This unsigned one-byte value is equivalent to the signed RAW Wave Value (16-bit) described below, except that it is scaled to be unsigned, and only the most significant 8 bits are output (where “most significant” is defined based on the specific ThinkGear hardware). This makes it possible to output raw wave values given the bandwidth restrictions of serial communications at a 9600 baud rate, at the cost of not outputting the lowest couple bits of precision. For many applications (such as realtime display of the graph of the raw wave), showing 8 bits of precision is sufficient, since the human eye typically cannot rapidly discern pixels which may correspond to the lower bits of precision anyways. If more precision is required, consider using the normal signed RAW Wave Value (16-bit) (described below) output at a higher baud rate.

Although only the most significant 8 bits are output when 8BIT_RAW output is enabled, all calculations are still performed within the ThinkGear based on the maximum precision of raw wave information available to the ThinkGear hardware, so no information is discarded internally. Only the outputted raw value is reduced to 8 bits, to save serial bandwidth.

By default, output of this Data Value is disabled. Like the regular signed 16-bit RAW Wave Value, the 8BIT_RAW Wave Value is typically output 128 times a second, or approximately once every 7.8 ms.

This Data Value is only available in ThinkGear Embedded modules (TGEM). It is not available from ThinkGear ASIC (TGAT/TGAM1), which is used in MindWave and MindWave Mobile.

RAW_MARKER Section Start

This is not really a Data Value, and is primarily only useful for debugging very precise timing and synchronization of the raw wave, or research purposes. Currently, the value will always be 0x00.

By default, output of this Data Value is disabled. It is typically output once a second.

This Data Value is only available in ThinkGear modules. It is not available from ThinkGear ASIC (such as MindWave and MindWave Mobile).

RAW Wave Value (16-bit)

This Data Value consists of two bytes, and represents a single raw wave sample. Its value is a signed 16-bit integer that ranges from -32768 to 32767. The first byte of the Value represents the high-order bits of the twos-compliment value, while the second byte represents the low-order bits. To reconstruct the full raw wave value, simply shift the first byte left by 8 bits, and bitwise-or with the second byte:

short raw = (Value[0]<<8) | Value[1];

where Value[0] is the high-order byte, and Value[1] is the low-order byte.

In systems or languages where bit operations are inconvenient, the following arithmetic operations may be substituted instead:

raw = Value[0]*256 + Value[1];
if( raw >= 32768 ) raw = raw - 65536;

where raw is of any signed number type in the language that can represent all the numbers from -32768 to 32767.

However, in Java you use the following code instead in order to avoid problems when implementing the parsing of CODE 0x80 (Raw Wave Data). This is due to the fact that Java has no unsigned data types, which has to be handled carefully when combining the two bytes.

private int[]   highlow = new int[2];
highlow[0] = (int)(payload[index] & 0xFF);
highlow[1] = (int)(payload[index+1] & 0xFF);
raw = (highlow[0] * 256) + highlow[1];
if( raw > 32768 ) raw -= 65536;
 

Each ThinkGear model reports its raw wave information in only certain areas of the full -32768 to 32767 range. For example, ThinkGear ASIC may only report raw waves that fall between approximately -2048 to 2047, while ThinkGear modules may only report raw waves that fall between approximately 0 to 1023. Please consult the documentation for your particular ThinkGear hardware for more information.

For TGEM the output of this Data Value is disabled by default. When enabled, the RAW Wave Value is typically outputted 128 times a second, or approximately once every 7.8ms. The ThinkGear ASIC (TGAT/TGAM1), however, outputs this value 512 times a second, or approximately once every 2ms. And it is usually turned on by default. The default can be adjusted at the module level, so please consult the spec sheet for more details.

Because of the high rate at which this value is output, and the number of bytes of data involved, it is only possible to output the 16-bit RAW Wave Value on the serial communication stream at 57,600 baud and above. If raw wave information at 9600 baud is desired, consider the 8BIT_RAW Wave Value output instead (described above).

EEG_POWER

This Data Value represents the current magnitude of 8 commonly-recognized types of EEG frequency bands (brainwaves). It consists of eight 4-byte floating point numbers in the following order: delta (0.5 - 2.75Hz), theta (3.5 - 6.75Hz), low-alpha (7.5 - 9.25Hz), high-alpha (10 - 11.75Hz), low-beta (13 - 16.75Hz), high-beta (18 - 29.75Hz), low-gamma (31 - 39.75Hz), and mid-gamma (41 - 49.75Hz). These values have no units and therefore are only meaningful when compared to each other and to themselves, for considering relative quantity and temporal fluctuations. The floating point format is standard big-endian IEEE 754, so the 32 bytes of the Values can therefore be directly cast as a float* in C (on big-endian environments) to be used as an array of floats.

By default, output of this Data Value is disabled. When enabled, it is typically output once a second.

This version of EEG_POWER, using floating point numbers, is only available in ThinkGear modules, and not in ASIC. For the ASIC equivalent, see ASIC_EEG_POWER_INT. As of ThinkGear Firmware v1.7.8, the ASIC version is the standard and preferred format for reading EEG band powers, and this floating point format is deprecated to backwards-compatibility purposes only.

ASIC_EEG_POWER_INT

This Data Value represents the current magnitude of 8 commonly-recognized types of EEG (brainwaves). It is the ASIC equivalent of EEG_POWER, with the main difference being that this Data Value is output as a series of eight 3-byte unsigned integers instead of 4-byte floating point numbers. These 3-byte unsigned integers are in big-endian format.

The eight EEG powers are output in the following order: delta (0.5 - 2.75Hz), theta (3.5 - 6.75Hz), low-alpha (7.5 - 9.25Hz), high-alpha (10 - 11.75Hz), low-beta (13 - 16.75Hz), high-beta (18 - 29.75Hz), low-gamma (31 - 39.75Hz), and mid-gamma (41 - 49.75Hz). These values have no units and therefore are only meaningful compared to each other and to themselves, to consider relative quantity and temporal fluctuations.

By default, output of this Data Value is enabled, and is typically output once a second.

As of ThinkGear Firmware v1.7.8, this form of EEG_POWER is the standard output format for EEG band powers, while the one described in EEG_POWER is only kept for backwards compatibility and only accessible through command switches. Prior to v1.7.8, the EEG_POWER was the standard.

This data value is currently unavailable via the ThinkGear Serial Stream APIs. It is not directly available as output from any current ThinkGear hardware. For TGCD, see the TG_DATA_BLINK_STRENGTH data type for use with the TG_GetValueStatus() and TG_GetValue() functions.

Mind-wandering Level

This unsigned one byte value reports the intensity of the user's Mind-wandering Level. Its value ranges from 0 to 10. A value of 0 means the Level is N/A. A value from 1-10 indicates the Level (with higher values indicating higher levels of Mind-wandering.

ThinkGear Packets

ThinkGear components deliver their digital data as an asynchronous serial stream of bytes. The serial stream must be parsed and interpreted as ThinkGear Packets in order to properly extract and interpret the ThinkGear Data Values described in the chapter above.

A ThinkGear Packet is a packet format consisting of 3 parts:

  1. Packet Header
  2. Packet Payload
  3. Payload Checksum

ThinkGear Packets are used to deliver Data Values (described in the previous chapter) from a ThinkGear module to an arbitrary receiver (a PC, another microprocessor, or any other device that can receive a serial stream of bytes). Since serial I/O programming APIs are different on every platform, operating system, and language, it is outside the scope of this document (see your platform's documentation for serial I/O programming). This chapter will only cover how to interpret the serial stream of bytes into ThinkGear Packets, Payloads, and finally into the meaningful Data Values described in the previous chapter.

The Packet format is designed primarily to be robust and flexible: Combined, the Header and Checksum provide data stream synchronization and data integrity checks, while the format of the Data Payload ensures that new data fields can be added to (or existing data fields removed from) the Packet in the future without breaking any Packet parsers in any existing applications/devices. This means that any application that implements a ThinkGear Packet parser properly will be able to use newer models of ThinkGear modules most likely without having to change their parsers or application at all, even if the newer ThinkGear includes new data fields.

Packet Structure

Packets are sent as an asynchronous serial stream of bytes. The transport medium may be UART, serial COM, USB, bluetooth, file, or any other mechanism which can stream bytes.

Each Packet begins with its Header, followed by its Data Payload, and ends with the Payload's Checksum Byte, as follows:

  [SYNC] [SYNC] [PLENGTH]  [PAYLOAD...]     [CHKSUM]
  _______________________  _____________  ____________
  ^^^^^^^^(Header)^^^^^^^  ^^(Payload)^^  ^(Checksum)^

The [PAYLOAD…] section is allowed to be up to 169 bytes long, while each of [SYNC], [PLENGTH], and [CHKSUM] are a single byte each. This means that a complete, valid Packet is a minimum of 4 bytes long (possible if the Data Payload is zero bytes long, i.e. empty) and a maximum of 173 bytes long (possible if the Data Payload is the maximum 169 bytes long).

A procedure for properly parsing ThinkGear Packets is given below in Step-By-Step Guide to Parsing a Packet.

Packet Header

The Header of a Packet consists of 3 bytes: two synchronization [SYNC] bytes (0xAA 0xAA), followed by a [PLENGTH] (Payload length) byte:

  [SYNC] [SYNC] [PLENGTH]
  _______________________
  ^^^^^^^^(Header)^^^^^^^

The two [SYNC] bytes are used to signal the beginning of a new arriving Packet and are bytes with the value 0xAA (decimal 170). Synchronization is two bytes long, instead of only one, to reduce the chance that [SYNC] (0xAA) bytes occurring within the Packet could be mistaken for the beginning of a Packet. Although it is still possible for two consecutive [SYNC] bytes to appear within a Packet (leading to a parser attempting to begin parsing the middle of a Packet as the beginning of a Packet) the [PLENGTH] and [CHKSUM] combined ensure that such a “mis-sync'd Packet” will never be accidentally interpreted as a valid packet (see Payload Checksum below for more details).

The [PLENGTH] byte indicates the length, in bytes, of the Packet's Data Payload [PAYLOAD…] section, and may be any value from 0 up to 169. Any higher value indicates an error (PLENGTH TOO LARGE). Be sure to note that [PLENGTH] is the length of the Packet's Data Payload, NOT of the entire Packet. The Packet's complete length will always be [PLENGTH] + 4.

Data Payload

The Data Payload of a Packet is simply a series of bytes. The number of Data Payload bytes in the Packet is given by the [PLENGTH] byte from the Packet Header. The interpretation of the Data Payload bytes into the ThinkGear Data Values described in Chapter 1 is defined in detail in the Data Payload Structure section below. Note that parsing of the Data Payload typically should not even be attempted until after the Payload Checksum Byte [CHKSUM] is verified as described in the following section.

Payload Checksum

The [CHKSUM] Byte must be used to verify the integrity of the Packet's Data Payload. The Payload's Checksum is defined as:

  1. summing all the bytes of the Packet's Data Payload
  2. taking the lowest 8 bits of the sum
  3. performing the bit inverse (one's compliment inverse) on those lowest 8 bits

A receiver receiving a Packet must use those 3 steps to calculate the checksum for the Data Payload they received, and then compare it to the [CHKSUM] Checksum Byte received with the Packet. If the calculated payload checksum and received [CHKSUM] values do not match, the entire Packet should be discarded as invalid. If they do match, then the receiver may procede to parse the Data Payload as described in the “Data Payload Structure” section below.

Data Payload Structure

Once the Checksum of a Packet has been verified, the bytes of the Data Payload can be parsed. The Data Payload itself consists of a continuous series of Data Values, each contained in a series of bytes called a DataRow. Each DataRow contains information about what the Data Value represents, the length of the Data Value, and the bytes of the Data Value itself. Therefore, to parse a Data Payload, one must parse each DataRow from it until all bytes of the Data Payload have been parsed.

DataRow Format

A DataRow consists of bytes in the following format:

  ([EXCODE]...) [CODE]  ([VLENGTH])  [VALUE...]
  ____________________ ____________ ___________
  ^^^^(Value Type)^^^^ ^^(length)^^ ^^(value)^^

Bytes in parentheses are conditional, meaning that they only appear in some DataRows, and not in others. See the following description for details.

The DataRow may begin with zero or more [EXCODE] (Extended Code) bytes, which are bytes with the value 0x55. The number of [EXCODE] bytes indicates the Extended Code Level. The Extended Code Level, in turn, is used in conjuction with the [CODE] byte to determine what type of Data Value this DataRow contains. Parsers should therefore always begin parsing a DataRow by counting the number of [EXCODE] (0x55) bytes that appear to determine the Extended Code Level of the DataRow's [CODE].

The [CODE] byte, in conjunction with the Extended Code Level, indicates the type of Data Value encoded in the DataRow. For example, at Extended Code Level 0, a [CODE] of 0x04 indicates that the DataRow contains an eSense Attention value. For a list of defined [CODE] meanings, see the CODE Definitions Table below. Note that the [EXCODE] byte of 0x55 will never be used as a [CODE] (incidentally, the [SYNC] byte of 0xAA will never be used as a [CODE] either).

If the [CODE] byte is between 0x00 and 0x7F, then the the [VALUE…] is implied to be 1 byte long (referred to as a Single-Byte Value). In this case, there is no [VLENGTH] byte, so the single [VALUE] byte will appear immediately after the [CODE] byte.

If, however, the [CODE] is greater than 0x7F, then a [VLENGTH] (“Value Length”) byte immediately follows the [CODE] byte, and this is the number of bytes in [VALUE…] (referred to as a Multi-Byte Value). These higher CODEs are useful for transmitting arrays of values, or values that cannot be fit into a single byte.

The DataRow format is defined in this way so that any properly implemented parser will not break in the future if new CODEs representing arbitrarily long DATA… values are added (they simply ignore unrecognized CODEs, but do not break in parsing), the order of CODEs is rearranged in the Packet, or if some CODEs are not always transmitted in every Packet.

A procedure for properly parsing Packets and DataRows is given below in Step-By-Step Guide to Parsing a Packet and Step-By-Step Guide to Parsing DataRows in a Packet Payload, respectively.

CODE Definitions Table

Single-Byte CODEs

Extended             (Byte)
Code Level  [CODE]  [LENGTH]  Data Value Meaning
----------  ------  --------  ------------------
         0    0x02         -  POOR_SIGNAL Quality (0-255)
         0    0x03         -  HEART_RATE (0-255)
                                Once/s on EGO.
         0    0x04         -  ATTENTION eSense (0 to 100)
         0    0x05         -  MEDITATION eSense (0 to 100)
         0    0x06         -  8BIT_RAW Wave Value (0-255)
         0    0x07         -  RAW_MARKER Section Start (0)

Multi-Byte CODEs

Extended             (Byte)
Code Level  [CODE]  [LENGTH]  Data Value Meaning
----------  ------  --------  ------------------
         0    0x80         2  RAW Wave Value: a single big-endian
                                16-bit two's-compliment signed value 
                                (high-order byte followed by 
                                low-order byte) (-32768 to 32767)
         0    0x81        32  EEG_POWER: eight big-endian 4-byte 
                                IEEE 754 floating point values
                                representing delta, theta, low-alpha
                                high-alpha, low-beta, high-beta, 
                                low-gamma, and mid-gamma EEG band
                                power values
         0    0x83        24  ASIC_EEG_POWER: eight big-endian 
                                3-byte unsigned integer values 
                                representing delta, theta, low-alpha
                                high-alpha, low-beta, high-beta, 
                                low-gamma, and mid-gamma EEG band
                                power values
         0    0x86         2  RRINTERVAL: two byte big-endian unsigned
                                integer representing the milliseconds
                                between two R-peaks        
       Any    0x55         -  NEVER USED (reserved for [EXCODE])
       Any    0xAA         -  NEVER USED (reserved for [SYNC])

(any Extended Code Level/CODE combinations not listed in the table above have not yet been defined, but may be added at any time in the future)

For detailed explanations of the meanings of each type of Data Value, please refer to the chapter on ThinkGear Data Values.

Whenever a ThinkGear module is powered on, it will always start in a standard configuration in which only some of the Data Values listed above will be output by default. To enable or disable the types of Data Values output by the ThinkGear, refer to the advanced chapter ThinkGear Command Bytes.

Example Packet

The following is a typical Packet. Aside from the [SYNC], [PLENGTH], and [CHKSUM] bytes, all the other bytes (bytes [ 3] to [10]) are part of the Packet's Data Payload. Note that the DataRows within the Payload are not guaranteed to appear in every Packet, nor are any DataRows that do appear guaranteed by the Packet specification to appear in any particular order.

byte: value // Explanation

[ 0]: 0xAA  // [SYNC]
[ 1]: 0xAA  // [SYNC]
[ 2]: 0x08  // [PLENGTH] (payload length) of 8 bytes
[ 3]: 0x02  // [CODE] POOR_SIGNAL Quality
[ 4]: 0x20  // Some poor signal detected (32/255)
[ 5]: 0x01  // [CODE] BATTERY Level
[ 6]: 0x7E  // Almost full 3V of battery (126/127)
[ 7]: 0x04  // [CODE] ATTENTION eSense
[ 8]: 0x12  // eSense Attention level of 18%
[ 9]: 0x05  // [CODE] MEDITATION eSense
[10]: 0x60  // eSense Meditation level of 96%
[11]: 0xE3  // [CHKSUM] (1's comp inverse of 8-bit Payload sum of 0x1C)

Step-By-Step Guide to Parsing a Packet

  1. Keep reading bytes from the stream until a [SYNC] byte (0xAA) is encountered.
  2. Read the next byte and ensure it is also a [SYNC] byte
    • If not a [SYNC] byte, return to step 1.
    • Otherwise, continue to step 3.
  3. Read the next byte from the stream as the [PLENGTH].
    • If [PLENGTH] is 170 ([SYNC]), then repeat step 3.
    • If [PLENGTH] is greater than 170, then return to step 1 (PLENGTH TOO LARGE).
    • Otherwise, continue to step 4.
  4. Read the next [PLENGTH] bytes of the [PAYLOAD…] from the stream, saving them into a storage area (such as an unsigned char payload[256] array). Sum up each byte as it is read by incrementing a checksum accumulator (checksum += byte).
  5. Take the lowest 8 bits of the checksum accumulator and invert them. Here is the C code:
         checksum &= 0xFF;
         checksum = ~checksum & 0xFF; 
  6. Read the next byte from the stream as the[CHKSUM] byte.
    • If the [CHKSUM] does not match your calculated chksum (CHKSUM FAILED).
    • Otherwise, you may now parse the contents of the Payload into DataRows to obtain the Data Values, as described below.
    • In either case, return to step 1.

Step-By-Step Guide to Parsing DataRows in a Packet Payload

Repeat the following steps for parsing a DataRow until all bytes in the payload[] array ([PLENGTH] bytes) have been considered and parsed:

  1. Parse and count the number of [EXCODE] (0x55) bytes that may be at the beginning of the current DataRow.
  2. Parse the [CODE] byte for the current DataRow.
  3. If [CODE] >= 0x80, parse the next byte as the [VLENGTH] byte for the current DataRow.
  4. Parse and handle the [VALUE…] byte(s) of the current DataRow, based on the DataRow's [EXCODE] level, [CODE], and [VLENGTH].
  5. If not all bytes have been parsed from the payload[] array, return to step 1. to continue parsing the next DataRow.

Sample C Code for Parsing a Packet

The following is an example of a program, implemented in C, which reads from a stream and (correctly) parses Packets continuously. Search for the word TODO for the two sections which would need to be modified to be appropriate for your application.

Note: For simplicity, error checking and handling for standard library function calls have been omitted. A real application should probably detect and handle all errors gracefully.

#include <stdio.h>
 
#define SYNC   0xAA
#define EXCODE 0x55
 
int parsePayload( unsigned char *payload, unsigned char pLength ) {
 
    unsigned char bytesParsed = 0;
    unsigned char code;
    unsigned char length;
    unsigned char extendedCodeLevel;
    int i;
 
    /* Loop until all bytes are parsed from the payload[] array... */
    while( bytesParsed < pLength ) {
 
        /* Parse the extendedCodeLevel, code, and length */
        extendedCodeLevel = 0;
        while( payload[bytesParsed] == EXCODE ) {
            extendedCodeLevel++;
            bytesParsed++;
        }
        code = payload[bytesParsed++];
        if( code & 0x80 ) length = payload[bytesParsed++];
        else              length = 1;
 
        /* TODO: Based on the extendedCodeLevel, code, length,
         * and the [CODE] Definitions Table, handle the next
         * "length" bytes of data from the payload as
         * appropriate for your application.
         */
        printf( "EXCODE level: %d CODE: 0x%02X length: %d\n",
                extendedCodeLevel, code, length );
        printf( "Data value(s):" );
        for( i=0; i<length; i++ ) {
            printf( " %02X", payload[bytesParsed+i] & 0xFF );
        }
        printf( "\n" );
 
        /* Increment the bytesParsed by the length of the Data Value */
        bytesParsed += length;
    }
 
    return( 0 );
}
 
int main( int argc, char **argv ) {
 
    int checksum;
    unsigned char payload[256];
    unsigned char pLength;
    unsigned char c;
    unsigned char i;
 
    /* TODO: Initialize 'stream' here to read from a serial data
     * stream, or whatever stream source is appropriate for your
     * application.  See documentation for "Serial I/O" for your
     * platform for details.
     */
    FILE *stream = 0;
    stream = fopen( "COM4", "r" );
 
    /* Loop forever, parsing one Packet per loop... */
    while( 1 ) {
 
        /* Synchronize on [SYNC] bytes */
        fread( &c, 1, 1, stream );
        if( c != SYNC ) continue;
        fread( &c, 1, 1, stream );
        if( c != SYNC ) continue;
 
        /* Parse [PLENGTH] byte */
        while( true ) {
            fread( &pLength, 1, 1, stream );
            if( pLength ~= 170 ) break;
        }
        if( pLength > 169 ) continue;
 
        /* Collect [PAYLOAD...] bytes */
        fread( payload, 1, pLength, stream );
 
        /* Compute [PAYLOAD...] chksum */
        checksum = 0;
        for( i=0; i<pLength; i++ ) checksum += payload[i];
        checksum &= 0xFF;
        checksum = ~checksum & 0xFF;
 
        /* Parse [CKSUM] byte */
        fread( &c, 1, 1, stream );
 
        /* Verify [PAYLOAD...] chksum against [CKSUM] */
        if( c != checksum ) continue;
 
        /* Since [CKSUM] is OK, parse the Data Payload */
        parsePayload( payload, pLength );
    }
 
    return( 0 );
}

ThinkGearStreamParser C API

The ThinkGearStreamParser API is a library which implements the parsing procedure described above and abstracts it into two simple functions, so that the programmer does not need to worry about parsing Packets and DataRows at all. All that is left is for the programmer to get the bytes from the data stream, stuff them into the parser, and then define what their program does with the Value[] bytes from each DataRow that is received and parsed.

The source code for the ThinkGearStreamParser API is provided, and consists of a .h header file and a .c source file. It is implemented in pure ANSI C for maximum portability to all platforms (including microprocessors).

Using the API consists of 3 steps:

  1. Define a data handler (callback) function which handles (acts upon) Data Values as they're received and parsed.
  2. Initialize a ThinkGearStreamParser struct by calling the THINKGEAR_initParser() function.
  3. As each byte is received from the data stream, the program passes it to the THINKGEAR_parseByte() function. This function will automatically call the data handler function defined in 1) whenever a Data Value is parsed.

The following subsections are excerpts from the ThinkGearStreamParser.h header file, which serves as the API documentation.

Constants

/* Parser types */
#define PARSER_TYPE_NULL       0x00
#define PARSER_TYPE_PACKETS    0x01 /* Stream bytes as ThinkGear Packets */
#define PARSER_TYPE_2BYTERAW   0x02 /* Stream bytes as 2-byte raw data */
 
/* Data CODE definitions */
#define PARSER_BATTERY_CODE         0x01
#define PARSER_POOR_SIGNAL_CODE     0x02
#define PARSER_ATTENTION_CODE       0x04
#define PARSER_MEDITATION_CODE      0x05
#define PARSER_RAW_CODE             0x80

THINKGEAR_initParser()

/**
 * @param parser              Pointer to a ThinkGearStreamParser object.
 * @param parserType          One of the PARSER_TYPE_* constants defined 
 *                            above: PARSER_TYPE_PACKETS or 
 *                            PARSER_TYPE_2BYTERAW.
 * @param handleDataValueFunc A user-defined callback function that will
 *                            be called whenever a data value is parsed
 *                            from a Packet.
 * @param customData          A pointer to any arbitrary data that will
 *                            also be passed to the handleDataValueFunc
 *                            whenever a data value is parsed from a
 *                            Packet.
 *
 * @return -1 if @c parser is NULL.
 * @return -2 if @c parserType is invalid.
 * @return 0 on success.
 */
int
THINKGEAR_initParser( ThinkGearStreamParser *parser, unsigned char parserType,
                      void (*handleDataValueFunc)(
                          unsigned char extendedCodeLevel,
                          unsigned char code, unsigned char numBytes,
                          const unsigned char *value, void *customData),
                      void *customData );

THINKGEAR_parseByte()

/**
 * @param parser Pointer to an initialized ThinkGearDataParser object.
 * @param byte   The next byte of the data stream.
 *
 * @return -1 if @c parser is NULL.
 * @return -2 if a complete Packet was received, but the checksum failed.
 * @return 0 if the @c byte did not yet complete a Packet.
 * @return 1 if a Packet was received and parsed successfully.
 *
 */
int
THINKGEAR_parseByte( ThinkGearStreamParser *parser, unsigned char byte );

Example

Here is an example program using the ThinkGearStreamParser API. It is very similar to the example program described above, simply printing received Data Values to stdout:

#include <stdio.h>
#include "ThinkGearStreamParser.h"
 
/**
 * 1) Function which acts on the value[] bytes of each ThinkGear DataRow as it is received.
 */
void
handleDataValueFunc( unsigned char extendedCodeLevel,
                     unsigned char code,
                     unsigned char valueLength,
                     const unsigned char *value,
                     void *customData ) {
 
    if( extendedCodeLevel == 0 ) {
 
        switch( code ) {
 
            /* [CODE]: ATTENTION eSense */
            case( 0x04 ):
                printf( "Attention Level: %d\n", value[0] & 0xFF );
                break;
 
            /* [CODE]: MEDITATION eSense */
            case( 0x05 ):
                printf( "Meditation Level: %d\n", value[0] & 0xFF );
                break;
 
            /* Other [CODE]s */
            default:
                printf( "EXCODE level: %d CODE: 0x%02X vLength: %d\n",
                        extendedCodeLevel, code, valueLength );
                printf( "Data value(s):" );
                for( i=0; i<valueLength; i++ ) printf( " %02X", value[i] & 0xFF );
                printf( "\n" );
        }
    }
}
 
/**
 * Program which reads ThinkGear Data Values from a COM port.
 */
int
main( int argc, char **argv ) {
 
    /* 2) Initialize ThinkGear stream parser */
    ThinkGearStreamParser parser;
    THINKGEAR_initParser( &parser, PARSER_TYPE_PACKETS,
                          handleDataValueFunc, NULL );
 
    /* TODO: Initialize 'stream' here to read from a serial data
     * stream, or whatever stream source is appropriate for your
     * application.  See documentation for "Serial I/O" for your
     * platform for details.
     */
    FILE *stream = fopen( "COM4", "r" );
 
    /* 3) Stuff each byte from the stream into the parser.  Every time
     *    a Data Value is received, handleDataValueFunc() is called.
     */
    unsigned char streamByte;
    while( 1 ) {
        fread( &streamByte, 1, stream );
        THINKGEAR_parseByte( &parser, streamByte );
    }
}

A few things to note:

  • The handleDataValueFunc() callback should be implemented to execute quickly, so as not to block the thread which is reading from the data stream. A more robust (and useful) program would probably spin off the thread which reads from the data stream and calls handleDataValueFunc(), and define handleDataValueFunc() to simply save the Data Values it receives, while the main thread actually uses the saved values for displaying to screen, controlling a game, etc. Threading is outside the scope of this manual.
  • The code for opening a serial communication port data stream for reading varies by operating system and platform. Typically, it is very similar to opening a normal file for reading. Serial communication is outside the scope of this manual, so please consult the documentation for “Serial I/O” for your platform for details. As an alternative, you may use the ThinkGear Communications Driver (TGCD) API, which can take care of opening and reading from serial I/O streams on some platforms for you. Use of that interface is described in the developer_tools_2.2_development_guide and TGCD API documentation.
  • Most error handling has been omitted from the above code for clarity. A properly written program should check all error codes returned by functions. Please consult the ThinkGearStreamParser.h header file for details about function parameters and return values.

ThinkGear Command Bytes

Upon power-on, ThinkGear modules/AISCs always start in their factory-programmed default state. For example, ThinkGear modules with FWv1.7.13 and earlier always start at 9600 baud, and outputting only battery, poor signal, ASIC_EEG, Attention, and Meditation values. This configuration is intended to be sufficient for most toy, game, and demo applications. After power-on however, the ThinkGear module can be sent Command Bytes in order to change its configuration, such as switching to 57.6k baud, or enabling raw wave values output. ThinkGear Command Bytes are intended as an advanced feature for performing some customization of the behavior ThinkGear hardware.

ThinkGear Command Bytes are sent to the ThinkGear hardware through the same UART interface used to receive Packet bytes. A Command Byte is a single byte (8-bit) value with certain bits set. This section describes which bits to set in order to change the configuration of a ThinkGear module.

Note that after being power cycled, the ThinkGear module returns to its default settings described above.

Also note that, before an application sends any command bytes to the ThinkGear, it should first make sure it has read at least one complete, valid Packet from the ThinkGear, to ensure it sends Command Bytes at the proper baudrate. Sending Command Bytes at the wrong baudrate may result in the ThinkGear being rendered inoperable until it is power-cycled (reset back to default configuration).

Command Byte Syntax

A Command Byte is formed by 8 bits, each of which are either set or unset. The lowest (least significant) four bits are used to control modes, such as 9600 vs. 57.6k baud mode, attention output enabled or disabled mode, etc. The upper (most significant) four bits, known as the “Command Page”, define the meaning of the lower four bits.

For example, a Command Byte of 0x0E has the bit pattern of 0000 1110. The upper (most significant) four bits are 0000, which refers to Command Page zero. Looking on the table below for Command Page zero (for 1.6 firmware), we see that the lower four bits of 1110 are used to control the settings for baudrate, raw wave output, meditation output, and attention output, respectively. Because the baudrate bit is 1, the baudrate of the module will be set to 57.6k mode. Because the raw wave output and meditation output bits are each 1, each of those types of output will be enabled. Because the attention output bit is 0, attention output becomes disabled. Hence, sending a byte of 0x0E to the ThinkGear module instructs it to operate at 57.6k baud mode with raw and meditation values output in packets, but no attention values.

Please note that the ordering of the Command Pages significantly changed between versions 1.6 and 1.7 of the module firmware. Also note that ThinkGear ASIC (such as MindWave and MindWave Mobile) only recognize the 4 Command Bytes on Page 0 for 1.7 firmware; all other Command Bytes may put the ThinkGear ASIC into an inoperable state until it is power-cycled. Please refer to the documentation that came with your ThinkGear hardware, along with the appropriate table below to determine which Command Bytes are valid for your hardware.

Firmware 1.6 Command Byte Table

  Page  0 (0000____) (0x0_): **
      bit[0] (____0001): Set/unset to enable/disable attention output
      bit[1] (____0010): Set/unset to enable/disable meditation output
      bit[2] (____0100): Set/unset to enable/disable raw wave output
      bit[3] (____1000): Set/unset to use 57.6k/9600 baud rate
  Page  1 (0001____) (0x1_):
      bit[0] (____0001): Set/unset to enable/disable EEG powers output
      bit[1] (____0010): Set/unset to use 10-bit/8-bit raw wave output
  Page 15 (1111____) (0xF_):
      bit[0] (____0001): Set/unset to enable/disable Testmode

**: After sending this Page byte, the application itself must change itself to begin communicating at the new requested baud rate, and then wait at that new requested baud rate for a complete, valid Packet to be received from the ThinkGear before attempting to send any other command bytes to the ThinkGear. Sending another command byte before performing this check may put the ThinkGear module into an indeterminate (and inoperable) state until it is power-cycled.

Firmware 1.7 Command Byte Table

ThinkGear ASIC only recognizes Command Bytes from Page 0 below. Any other Command Bytes may put it into an inoperable state until it is power cycled.

  Page  0 (0000____) (0x0_):  STANDARD/ASIC CONFIG COMMANDS* **
      00000000 (0x00): 9600 baud, normal output mode
      00000001 (0x01): 1200 baud, normal output mode
      00000010 (0x02): 57.6k baud, normal+raw output mode
      00000011 (0x03): 57.6k baud, FFT output mode
  Page  1 (0001____) (0x1_):  RAW WAVE OUTPUT
      bit[0] (____0001): Set/unset to enable/disable raw wave output
      bit[1] (____0010): Set/unset to use 10-bit/8-bit raw wave output
      bit[2] (____0100): Set/unset to enable/disable raw marker output
      bit[3] (____1000): Ignored
  Page  2 (0010____) (0x2_):  MEASUREMENTS OUTPUTS
      bit[0] (____0001): Set/unset to enable/disable poor quality output
      bit[1] (____0010): Set/unset to enable/disable EEG powers (int) output
      bit[2] (____0100): Set/unset to enable/disable EEG powers (legacy/floats) output
      bit[3] (____1000): Set/unset to enable/disable battery output***
  Page  3 (0011____) (0x3_):  ESENSE OUTPUTS
      bit[0] (____0001): Set/unset to enable/disable attention output
      bit[1] (____0010): Set/unset to enable/disable meditation output
      bit[2] (____0100): Ignored
      bit[3] (____1000): Ignored
  Page  6 (0110____) (0x6_):  BAUD RATE SELECTION* **
      01100000 (0x60): No change
      01100001 (0x61): 1200 baud
      01100010 (0x62): 9600 baud
      01100011 (0x63): 57.6k baud

*: Note that pages 0 and 6 are a little different from most command pages. While most pages use each of the 4 bits as an enable/disable switch commands for separate setting, pages 0 and 6 use the entire command value as a single command.

**: After sending this Page byte, the application itself must change itself to begin communicating at the new requested baud rate, and then wait at that new requested baud rate for a complete, valid Packet to be received from the ThinkGear before attempting to send any other command bytes to the ThinkGear. Sending another command byte before performing this check may put the ThinkGear module into an indeterminate (and inoperable) state until it is power-cycled.

***: Battery level sampling not available on some ThinkGear models.