Mindset: различия между версиями
(начало положено) |
Tasman (обсуждение | вклад) мНет описания правки |
||
(не показано 5 промежуточных версий этого же участника) | |||
Строка 4: | Строка 4: | ||
В данном руководстве вы узнаете: | В данном руководстве вы узнаете: | ||
* Как подключиться к | * Как подключиться к нейрогарнитуре по Bluetooth и провести сопряжение. | ||
* Как анализировать | * Как анализировать пакеты данных, чтобы восстановить различные типы мозговых волн. | ||
* Как интерпретировать и использовать различные типы данных о мозговых волнах, отправляемых с | * Как интерпретировать и использовать различные типы данных о мозговых волнах, отправляемых с нейрогарнитуры (включая данные о внимании, медитации и качестве сигнала). | ||
В главе «Значения данных | В главе «Значения данных нейрогарнитуры» определяются типы значений данных, о которых может сообщать нейрогарнитура. | ||
В главе «Пакеты нейрогарнитуры» описан формат пакетов. | |||
Значения данных в последовательном потоке ввода-вывода. | |||
== Подключение по Bluetooth == | |||
Гарнитура подключается к компьютеру или телефону через последовательный порт Bluetooth Clasic 4.0, используя стандартный профиль SPP. Передача данных идет пакетами в формате MindSet. | |||
Стоит отметить, что на текущий момент BLE (Bluetooth Low Energi) той же версии не поддерживается. | |||
Данные для подключения: | |||
• '''Профиль Bluetooth:''' Профиль последовательного порта (SPP). | • '''Профиль Bluetooth:''' Профиль последовательного порта (SPP). | ||
Строка 27: | Строка 27: | ||
• '''Пароль:''' 0000. | • '''Пароль:''' 0000. | ||
Для работы Bluetooth модуля на ПК могут потребоваться дополнительные драйверы, для телефона необходимо наличие Bluetooth и приложения поддерживающего протокол MindSet. | |||
== Значения данных нейрогарнитуры == | |||
== Значения данных | |||
=== POOR_SIGNAL Quality === | === POOR_SIGNAL Quality === | ||
Строка 309: | Строка 301: | ||
a Packet. | a Packet. | ||
=== | === Формат заголовка пакета === | ||
e Header of a Packet consists of 3 bytes: two synchronization [SYNC] bytes (0xAA 0xAA), followed | e Header of a Packet consists of 3 bytes: two synchronization [SYNC] bytes (0xAA 0xAA), followed | ||
by a [PLENGTH] (Payload length) byte: | by a [PLENGTH] (Payload length) byte: | ||
{| class="wikitable" | |||
|+Заголовок пакета | |||
!байт синхронизации | |||
!байт синхронизации | |||
!байт длины пакета | |||
|} | |||
e two [SYNC] bytes are used to signal the beginning of a new arriving Packet and are bytes with | e two [SYNC] bytes are used to signal the beginning of a new arriving Packet and are bytes with | ||
Строка 375: | Строка 366: | ||
described in the "Data Payload Structure" section below. | described in the "Data Payload Structure" section below. | ||
=== Data Payload Structure === | |||
Once the Checksum of a Packet has been veried, the bytes of the Data Payload can be parsed. e | |||
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. erefore, to parse a Data Payload, | |||
one must parse each DataRow from the Data Payload, until all bytes of the Data Payload have been | |||
parsed. | |||
=== DataRow Format === | |||
A DataRow consists of bytes in the following format: | |||
{| class="wikitable" | |||
|+ | |||
!([EXCODE]...) [CODE] | |||
!([VLENGTH]) | |||
![VALUE...] | |||
|- | |||
|^^^^(Value Type)^^^^ | |||
|^^(length)^^ | |||
|^^(value)^^ | |||
|} | |||
<blockquote>Note: Bytes in parentheses are conditional, meaning that they only appear in some DataRows, and | |||
not in others. See the following description for details.</blockquote>e DataRow may begin with zero or more [EXCODE] (Extended Code) bytes, which are bytes with | |||
the value 0x55. e number of [EXCODE] bytes indicates the Extended Code Level. e 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]. | |||
e [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 dened [CODE] meanings, see the | |||
CODE Denitions 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). ese higher CODEs are useful for transmitting arrays of values, or values that cannot be t | |||
into a single byte. | |||
e DataRow format is dened 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 === | |||
{| class="wikitable" | |||
|+ | |||
!Байт | |||
метка | |||
!Обозначение | |||
!Описание | |||
!Диапазон | |||
|- | |||
|0x02 | |||
|POOR_SIGNAL | |||
| Зашумленность сигнала | |||
|От 0 до 255 | |||
|- | |||
|0x04 | |||
|ATTENTION | |||
| Сосредоточенность | |||
|От 0 до 100 | |||
|- | |||
|0x05 | |||
|MEDITATION | |||
| Медитация | |||
|От 0 до 100 | |||
|- | |||
|0x16 | |||
|Blink | |||
| Сила сжатия мышц глаза при моргании. | |||
Данный пакет не посылается в момент расслабленных мышц. | |||
|От 0 до 255 | |||
|- | |||
|0x83 | |||
|ASIC_EEG_POWER_INT | |||
|Набор биоритмов мозга (альфа, бета...) | |||
|8 блоков по 3 байта | |||
|} | |||
=== Образец структуры пакета === | |||
e following is a typical packet. Aside from the [SYNC], [PLENGTH], and [CHKSUM] bytes, all the | |||
other bytes (bytes [ 3] to [34]) 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 specication to appear in any particular order. | |||
{| class="wikitable" | |||
|+ | |||
!Номер | |||
байта | |||
!Значение | |||
!Комментарий | |||
|- | |||
|0 | |||
|0xAA | |||
|байт синхронизации | |||
|- | |||
|1 | |||
|0xAA | |||
|байт синхронизации | |||
|- | |||
|2 | |||
|0x20 | |||
|длина тела ответа (32 байта) | |||
|- | |||
|3 | |||
|0x02 | |||
|зашумленность сигнала [POOR_SIGNAL] | |||
|- | |||
|4 | |||
|0x00 | |||
|зашумленный сигнал не обнаружен (0/200) | |||
|- | |||
|5 | |||
|0x83 | |||
|[ASIC_EEG_POWER_INT] | |||
|- | |||
|6 | |||
|0x18 | |||
|[VLENGTH] 24 байта | |||
|- | |||
|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) | |||
|} | |||
=== 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] (refer to the Code Denitions Table). | |||
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 === | |||
e 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 modied 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.<syntaxhighlight lang="c++" line="1"> | |||
#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 ); | |||
/* Calculate [PAYLOAD...] checksum */ | |||
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 [CKSUM] byte against calculated [PAYLOAD...] checksum */ | |||
if( c != checksum ) continue; | |||
/* Since [CKSUM] is OK, parse the Data Payload */ | |||
parsePayload( payload, pLength ); | |||
} | |||
return( 0 ); | |||
} | |||
</syntaxhighlight> | |||
=== ThinkGearStreamParser C API === | |||
e inkGearStreamParser 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 dene what their program does with the Value[] | |||
bytes from each DataRow that is received and parsed. | |||
e source code for the inkGearStreamParser API is provided as part of the MindSet Development | |||
Tools (MDT), and consists of a .h header le and a .c source le. It is implemented in pure ANSI C | |||
for maximum portability to all platforms (including microprocessors). | |||
Using the API consists of 3 steps: | |||
1. Dene 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. is function will automatically call the data handler function dened in 1) whenever | |||
a Data Value is parsed. | |||
e following subsections are excerpts from the ThinkGearStreamParser.h header le, which serves | |||
as the API documentation. | |||
=== Constants === | |||
/* Parser types */ | |||
<nowiki>#</nowiki>define PARSER_TYPE_NULL 0x00 | |||
<nowiki>#</nowiki>define PARSER_TYPE_PACKETS 0x01 /* Stream bytes as ThinkGear Packets */ | |||
<nowiki>#</nowiki>define PARSER_TYPE_2BYTERAW 0x02 /* Stream bytes as 2-byte raw data */ | |||
/* Data CODE definitions */ | |||
<nowiki>#</nowiki>define PARSER_BATTERY_CODE 0x01 | |||
<nowiki>#</nowiki>define PARSER_POOR_SIGNAL_CODE 0x02 | |||
<nowiki>#</nowiki>define PARSER_ATTENTION_CODE 0x04 | |||
<nowiki>#</nowiki>define PARSER_MEDITATION_CODE 0x05 | |||
<nowiki>#</nowiki>define PARSER_RAW_CODE 0x80 | |||
=== THINKGEAR_initParser() === | |||
/** | |||
<nowiki>*</nowiki> @param parser Pointer to a ThinkGearStreamParser object. | |||
<nowiki>*</nowiki> @param parserType One of the PARSER_TYPE_* constants defined | |||
<nowiki>*</nowiki> above: PARSER_TYPE_PACKETS or | |||
<nowiki>*</nowiki> PARSER_TYPE_2BYTERAW. | |||
<nowiki>*</nowiki> @param handleDataValueFunc A user-defined callback function that will | |||
<nowiki>*</nowiki> be called whenever a data value is parsed | |||
<nowiki>*</nowiki> from a Packet. | |||
<nowiki>*</nowiki> @param customData A pointer to any arbitrary data that will | |||
<nowiki>*</nowiki> also be passed to the handleDataValueFunc | |||
<nowiki>*</nowiki> whenever a data value is parsed from a | |||
<nowiki>*</nowiki> Packet. | |||
<nowiki>*</nowiki> | |||
<nowiki>*</nowiki> @return -1 if @c parser is NULL. | |||
<nowiki>*</nowiki> @return -2 if @c parserType is invalid. | |||
<nowiki>*</nowiki> @return 0 on success. | |||
<nowiki>*</nowiki>/ | |||
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() === | |||
/** | |||
<nowiki>*</nowiki> @param parser Pointer to an initialized ThinkGearDataParser object. | |||
<nowiki>*</nowiki> @param byte The next byte of the data stream. | |||
<nowiki>*</nowiki> | |||
<nowiki>*</nowiki> @return -1 if @c parser is NULL. | |||
<nowiki>*</nowiki> @return -2 if a complete Packet was received, but the checksum failed. | |||
<nowiki>*</nowiki> @return 0 if the @c byte did not yet complete a Packet. | |||
<nowiki>*</nowiki> @return 1 if a Packet was received and parsed successfully. | |||
<nowiki>*</nowiki> | |||
<nowiki>*</nowiki>/ | |||
int | |||
THINKGEAR_parseByte( ThinkGearStreamParser *parser, unsigned char byte ); | |||
=== Example === | |||
Here is an example program using the inkGearStreamParser API. It is very similar to the example | |||
program described above, simply printing received Data Values to stdout: | |||
<nowiki>#</nowiki>include <stdio.h> | |||
<nowiki>#</nowiki>include "ThinkGearStreamParser.h" | |||
/** | |||
<nowiki>*</nowiki> 1) Function which acts on the value[] bytes of each ThinkGear DataRow as it is received. | |||
<nowiki>*</nowiki>/ | |||
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" ); | |||
} | |||
} | |||
} | |||
/** | |||
<nowiki>*</nowiki> Program which reads ThinkGear Data Values from a COM port. | |||
<nowiki>*</nowiki>/ | |||
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 | |||
<nowiki>*</nowiki> stream, or whatever stream source is appropriate for your | |||
<nowiki>*</nowiki> application. See documentation for "Serial I/O" for your | |||
<nowiki>*</nowiki> platform for details. | |||
<nowiki>*</nowiki>/ | |||
FILE *stream = fopen( "COM4", "r" ); | |||
/* 3) Stuff each byte from the stream into the parser. Every time | |||
<nowiki>*</nowiki> a Data Value is received, handleDataValueFunc() is called. | |||
<nowiki>*</nowiki>/ | |||
unsigned char streamByte; | |||
while( 1 ) { | |||
fread( &streamByte, 1, stream ); | |||
THINKGEAR_parseByte( &parser, streamByte ); | |||
} | |||
} | |||
A few things to note: | |||
• e 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 dene 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. reading is outside the scope of this manual. | |||
• e 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 le 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 inkGear | |||
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.1\_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 ThinkGearStream- | |||
Parser.h header le for details about function parameters and return values. | |||
== Источник == | |||
[[Категория:Нейроинтерфейсы]] |
Текущая версия от 05:42, 11 февраля 2024
MindSet Communications Protocol - протокол разработанный компанией NeuroSky для обмена данными с одноканальными нейрогарнитурами по беспроводному подключению bluetooth.
Вступление[править | править код]
В данном руководстве вы узнаете:
- Как подключиться к нейрогарнитуре по Bluetooth и провести сопряжение.
- Как анализировать пакеты данных, чтобы восстановить различные типы мозговых волн.
- Как интерпретировать и использовать различные типы данных о мозговых волнах, отправляемых с нейрогарнитуры (включая данные о внимании, медитации и качестве сигнала).
В главе «Значения данных нейрогарнитуры» определяются типы значений данных, о которых может сообщать нейрогарнитура.
В главе «Пакеты нейрогарнитуры» описан формат пакетов.
Значения данных в последовательном потоке ввода-вывода.
Подключение по Bluetooth[править | править код]
Гарнитура подключается к компьютеру или телефону через последовательный порт Bluetooth Clasic 4.0, используя стандартный профиль SPP. Передача данных идет пакетами в формате MindSet.
Стоит отметить, что на текущий момент BLE (Bluetooth Low Energi) той же версии не поддерживается.
Данные для подключения:
• Профиль Bluetooth: Профиль последовательного порта (SPP).
• Скорость передачи данных: 57600 бод.
• Пароль: 0000.
Для работы Bluetooth модуля на ПК могут потребоваться дополнительные драйверы, для телефона необходимо наличие Bluetooth и приложения поддерживающего протокол MindSet.
Значения данных нейрогарнитуры[править | править код]
POOR_SIGNAL Quality[править | править код]
Это однобайтовое целое число без знака описывает, насколько плохим является сигнал, измеряемый inkGear. Это
Диапазон значений от 0 до 200. Любое ненулевое значение указывает на то, что имеется какое-то шумовое загрязнение.
обнаружен. Чем выше число, тем больше шума обнаруживается. Значение 200 имеет особое значение,
в частности, контакты inkGear не касаются кожи пользователя.
Это значение обычно выводится каждую секунду и указывает на неудовлетворительные результаты последних измерений.
Плохой сигнал может быть вызван множеством разных причин. В порядке серьезности они следующие:
• Датчик, заземление или опорные контакты не находятся на голове человека (т. е. когда никто не носит
inkGear).
• Плохой контакт датчика, заземления или опорных контактов с кожей человека (т. е. волосы мешают,
или гарнитура, которая неправильно прилегает к голове человека, или гарнитура неправильно закреплена на
голова).
• Чрезмерные движения пользователя (например, чрезмерные движения головой или телом, тряска гарнитуры).
• Чрезмерный электростатический шум окружающей среды (в некоторых средах присутствуют сильные электрические сигналы или
накопление статического электричества у человека, носящего датчик).
• Чрезмерный биометрический шум, не связанный с ЭЭГ (например, ЭМГ, ЭКГ/ЭКГ, ЭОГ и т. д.)
Определенное количество шума неизбежно при обычном использовании inkGear, и оба фильтра NeuroSky
технология и алгоритм eSense™ были разработаны для обнаружения, исправления, компенсации и учета
и терпят многие типы шумов, не связанных с ЭЭГ. Большинство типичных пользователей, которые заинтересованы только в использовании
ценностям eSense, таким как Внимание и Медитация, не нужно слишком беспокоиться о
POOR_SIGNAL Значение качества, за исключением того, что значения «Внимание» и «Медитация» не будут
обновляется при обнаружении POOR_SIGNAL. Значение качества POOR_SIGNAL более полезно для некоторых
приложения, которые должны быть более чувствительны к шуму (например, некоторые медицинские или исследовательские приложения),
или приложения, которые должны немедленно оповещать об обнаружении даже незначительного шума.
По умолчанию вывод этого значения данных включен. Обычно он выводится раз в секунду.
eSense™ 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 inkGear 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. ese 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 inkGear is unable to calculate an eSense
level with a reasonable amount of reliability. is may be (and usually is) due to excessive noise as
described in the POOR_SIGNAL Quality section above.
e 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 uctuations 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 uctuation. is is part of the
reason why inkGear 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 ne-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[править | править код]
is 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\texttrademark 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[править | править код]
is 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.
RAW Wave Value (16-bit)[править | править код]
is 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. e rst 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 rst 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.
Each inkGear model reports its raw wave information in only certain areas of the full -32768 to
32767 range. For example, MindSet reports raw waves that fall between approximately -2048 to 2047.
By default, output of this Data Value is enabled, and is outputed 512 times a second, or approximately
once every 2ms.
ASIC_EEG_POWER[править | править код]
is Data Value represents the current magnitude of 8 commonly-recognized types of EEG (brainwaves).
is Data Value is output as a series of eight 3-byte unsigned integers in little-endian format.
e 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). ese values have no
units and therefore are only meaningful compared to each other and to themselves, to consider relative
quantity and temporal uctuations.
By default, output of this Data Value is enabled, and is typically output once a second.
Blink Strength[править | править код]
is unsigned one byte value reports the intensity of the user's most recent eye blink. Its value ranges
from 1 to 255 and it is reported whenever an eye blink is detected. e value indicates the relative
intensity of the blink, and has no units.
Note: is data value is currently only available via the TGCD and TGC APIs. It is
not directly available as output from any current inkGear hardware. For TGCD, see the
TG_DATA_BLINK_STRENGTH data type for use with the TG_GetValueStatus() and TG_GetValue()
functions.
ThinkGear Packets[править | править код]
inkGear components deliver their digital data as an asynchronous serial stream of bytes. e serial
stream must be parsed and interpreted as inkGear Packets in order to properly extract and interpret
the inkGear Data Values described in the chapter above.
A inkGear Packet is a packet format consisting of 3 parts:
1. Packet Header
2. Packet Payload
3. Payload Checksum
inkGear Packets are used to deliver Data Values (described in the previous chapter) from a inkGear
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). is chapter will only cover how to interpret the serial stream of bytes into
inkGear Packets, Payloads, and nally into the meaningful Data Values described in the previous
chapter.
e Packet format is designed primarily to be robust and exible: Combined, the Header and Checksum
provide data stream synchronization and data integrity checks, while the format of the Data
Payload ensures that new data elds can be added to (or existing data elds removed from) the Packet
in the future without breaking any Packet parsers in any existing applications/devices. is means that
any application that implements a inkGear Packet parser properly will be able to use newer models
of inkGear modules most likely without having to change their parsers or application at all, even if
the newer inkGear hardware includes new data elds or rearranges the order of the data elds.
Packet Structure[править | править код]
Packets are sent as an asynchronous serial stream of bytes. e transport medium may be UART, serial
COM, USB, bluetooth, le, 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:
первый байт синхронизации | второй байт синхронизации | длина пакета | тело ответа | контрольная сумма |
---|---|---|---|---|
Заголовок пакета ^^^ | Данные ^^^ | Контрольная сумма ^^^ |
e [PAYLOAD…] section is allowed to be up to 169 bytes long, while each of [SYNC], [PLENGTH],
and [CHKSUM] are a single byte each. is 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 inkGear Packets is given below in Step-By-Step Guide to Parsing
a Packet.
Формат заголовка пакета[править | править код]
e Header of a Packet consists of 3 bytes: two synchronization [SYNC] bytes (0xAA 0xAA), followed
by a [PLENGTH] (Payload length) byte:
байт синхронизации | байт синхронизации | байт длины пакета |
---|
e 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).
e [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.
e Packet's complete length will always be [PLENGTH] + 4.
Data Payload[править | править код]
e Data Payload of a Packet is simply a series of bytes. e number of Data Payload bytes in the
Packet is given by the [PLENGTH] byte from the Packet Header. e interpretation of the Data Payload
bytes into the inkGear Data Values described in Chapter 1 is dened 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 veried as described in the following
section.
Payload Checksum[править | править код]
e [CHKSUM] Byte must be used to verify the integrity of the Packet's Data Payload. e Payload's
Checksum is dened 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 veried, the bytes of the Data Payload can be parsed. e
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. erefore, to parse a Data Payload,
one must parse each DataRow from the Data Payload, 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)^^ |
Note: Bytes in parentheses are conditional, meaning that they only appear in some DataRows, and not in others. See the following description for details.
e DataRow may begin with zero or more [EXCODE] (Extended Code) bytes, which are bytes with
the value 0x55. e number of [EXCODE] bytes indicates the Extended Code Level. e 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].
e [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 dened [CODE] meanings, see the
CODE Denitions 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). ese higher CODEs are useful for transmitting arrays of values, or values that cannot be t
into a single byte.
e DataRow format is dened 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[править | править код]
Байт
метка |
Обозначение | Описание | Диапазон |
---|---|---|---|
0x02 | POOR_SIGNAL | Зашумленность сигнала | От 0 до 255 |
0x04 | ATTENTION | Сосредоточенность | От 0 до 100 |
0x05 | MEDITATION | Медитация | От 0 до 100 |
0x16 | Blink | Сила сжатия мышц глаза при моргании.
Данный пакет не посылается в момент расслабленных мышц. |
От 0 до 255 |
0x83 | ASIC_EEG_POWER_INT | Набор биоритмов мозга (альфа, бета...) | 8 блоков по 3 байта |
Образец структуры пакета[править | править код]
e following is a typical packet. Aside from the [SYNC], [PLENGTH], and [CHKSUM] bytes, all the
other bytes (bytes [ 3] to [34]) 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 specication to appear in any particular order.
Номер
байта |
Значение | Комментарий |
---|---|---|
0 | 0xAA | байт синхронизации |
1 | 0xAA | байт синхронизации |
2 | 0x20 | длина тела ответа (32 байта) |
3 | 0x02 | зашумленность сигнала [POOR_SIGNAL] |
4 | 0x00 | зашумленный сигнал не обнаружен (0/200) |
5 | 0x83 | [ASIC_EEG_POWER_INT] |
6 | 0x18 | [VLENGTH] 24 байта |
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) |
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] (refer to the Code Denitions Table).
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[править | править код]
e 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 modied 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 );
/* Calculate [PAYLOAD...] checksum */
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 [CKSUM] byte against calculated [PAYLOAD...] checksum */
if( c != checksum ) continue;
/* Since [CKSUM] is OK, parse the Data Payload */
parsePayload( payload, pLength );
}
return( 0 );
}
ThinkGearStreamParser C API[править | править код]
e inkGearStreamParser 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 dene what their program does with the Value[]
bytes from each DataRow that is received and parsed.
e source code for the inkGearStreamParser API is provided as part of the MindSet Development
Tools (MDT), and consists of a .h header le and a .c source le. It is implemented in pure ANSI C
for maximum portability to all platforms (including microprocessors).
Using the API consists of 3 steps:
1. Dene 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. is function will automatically call the data handler function dened in 1) whenever
a Data Value is parsed.
e following subsections are excerpts from the ThinkGearStreamParser.h header le, 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 inkGearStreamParser 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:
• e 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 dene 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. reading is outside the scope of this manual.
• e 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 le 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 inkGear
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.1\_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 ThinkGearStream-
Parser.h header le for details about function parameters and return values.