Contec KT88-2400 - 24 channel EEG

Making & changing hardware drivers in Acquisition Server
a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Contec KT88-2400 - 24 channel EEG

Post by a1eks »

Hi,

I know there were topics about drivers for Contec devices, especially for the KT88-1600 (16ch EEG), which worked well (the source code in the attachment), however, there was nothing for the KT88-2400, and so I did some reverse engineering to figure out how to decode the commands. Here are my findings.

I will publish here the communication protocol, and it might be useful for somebody who is interested to contribute to the driver development.

Acquisition and filter control (commands in hex format)
90 01 Start Acquisition
90 02 Stop acquisition
90 03 Enable HW filter 0.5-35Hz
90 04 Disable HW filter
90 05 Start impedance measurement
90 06 Disable impedance measurement

Setting physical reference electrode
91 01 AA reference (left hemisphere referenced to the left ear lobe, right to the right earlobe)
91 02 A1 reference (all electrodes referenced to the A1)
91 03 A2 reference
91 04 AVG (average reference)
91 05 Cz reference
91 06 BN (balanced noncephalic reference)

Montage setup
92 0X (9 defined montages X=1,...,9, the montage can be explored by going to System configuration -> montage ways). Changes of default montage always follow the command 91 04 (avg reference).


Uknown commands
80 00
81 00

Example of the default system settings sequence sent by the provided EEG software.

90 02 //stop acquisition
80 00 //?
81 00 //?
91 01 //set AA reference
90 09 //?
90 03 //Enable HW filter
90 06 //Disable impedance reading
90 01 //Start data acquisition
... DATA STREAM ...
90 02 //Stop acquisition

Data Stream


Baud rate 921600 (has to be that one)
Data sampling rate 200Hz
Encoding bits 12
Number of channels 26

Stream format:

After executing (sending the command to the amplifier) 90 01, the data stream starts with two bytes e0 01 which indicate the start of the data streaming and it is followed by 46 bytes that carry the data for 26 channels. Each block of 46 bytes starts with a0 0 followed by 4 most significant bits of the channel number 1. The rest 8 bits (12bits total -4 =8) are taken from the consecutive byte. All consecutive bytes carry information only in 7 least significant bits (because the first bit is always reserved for the start of the sequence and cannot be used). To make this more illustrative I will try to represent it graphically below. Suppose that we have started the amplifier and it always gives us 4096 for all channels (so we have all "ones" for the data), then we have something like this:

2bytes
1110 0000 0000 0001

46bytes
1010 0000 0000 1111 0111 1111 0111 1111 0111 1111 0111 1111 0111 1 ...



* - data stream start

* -block start

* - channel 1 first sample

* - channel 2 first sample

* - channel 3 first sample
...
until channel 26.



In hex format, 3 samples

e0 01
a0 0f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f a0 0f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f a0 0f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f...


Now I would like to modify the code in the attachment (driver for KT88-1600) and I am struggling with the method CDriverContecKT88::getPacket, so I need help with how to properly shift bits, so I extract 12 bits of data and ignore the first bit. Maybe the problem is trivial, but I don't have much experience with bit shifts.

Thank you in advance!

EDIT: drivers source https://github.com/miladinovic/KT88-160 ... ibe-driver

Thomas
Posts: 210
Joined: Wed Mar 04, 2020 3:38 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by Thomas »

Hi A1eks,

I had a quick look online, and I see that the KT88 2400 is a 24 channels device, and not 26. (Are the two extra ground and reference?)

Also, any bit shifts and masking is possible, but I am a bit skeptical about the format you describe, notably about the 12 bits shifting across bytes.
How did you get the information about the reserved bit at the beginning of each sequence ?

Sorry for asking more questions rather than bringing answers.

Cheers,
Thomas

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

Hi Thomas,

Thanks for the help. As I have KT88-2400 I can confirm that there are 26channels available, and these two additional are PG1 and PG2 (nasopharyngeal electrodes), nowadays rarely used. In a certain type of montage setting, they do appear though in the official software.
How did you get the information about the reserved bit at the beginning of each sequence?
The data format is a nightmare, it took me weeks to figure it out. Finally, I did some calculations and then everything fitted perfectly, so I am pretty confident about it. The reserved bit has to be there because of the "0xA0" marker, and I confirmed it by spoofing serial communication and saturating amplifier, so I obtained a series of 7fs in 45bytes chunks.

But, that is not the end, I managed to implement bit shifting and masking in python, but then I realized that it is even worse than I thought. As in KT88-1600, the two consecutive bytes have to be swapped, to make it work.
// Each channel value is formed by two consecutive bytes, the first byte is
// the least significant one and the second the most significant one. That means
// we need to write both bytes into a 16 bit unsigned integer and swap its byte
// order
Comment from KT88-1600 driver https://github.com/miladinovic/KT88-160 ... ecKT88.cpp


This is the provisional decoding script I made tonight. It contains also a stream of data registered from the amplifier. The ECG is clearly visible on the last channel, but I don't know about the other channels if it decodes it properly. I will check it out tomorrow.



Image

Code: Select all

import io

file_object = open('/tmp/eeg.txt', 'w+')



def swap_bytes(b):
    bt=bytearray(b)
    a=bt[0]<<4 | (bt[1] & 0xF)
    bt[0]=bt[1]>>4
    bt[1]=a
    for i in range(2,44,2):
        a=bt[i]
        bt[i]=bt[i+1]
        bt[i+1]=a
    return  int.from_bytes(bt, byteorder='big')


def remove_bit(num, i):
    mask = num >> (i + 1)
    mask = mask << i
    right = ((1 << i) - 1) & num
    return mask | right

def insert_mult_bits(num, bits, len, i):
    mask = num >> i
    mask = (mask << len) | bits
    mask = mask << i
    right = ((1 << i) - 1) & num
    return right | mask


def decode_data(b):
    sample=swap_bytes(b)

    #remove unused bits
    corr=0;
    for i in range(7, sample.bit_length(), 8):
        sample=remove_bit(sample,i-corr);
        corr=corr+1;

    #add HSB to make 2byte representation
    corr=0;
    for i in range(12,sample.bit_length(),12):
        sample=insert_mult_bits(sample,0,4,i+corr)
        corr=corr+4;

    #convert to bytes 26channels x 2 bytes, bigendian
    bt=sample.to_bytes(26*2,'big');

    #assign the result to int list
    idx=0;
    out=[];
    for i in range(0,26*2-1,2):
        out.append(int(int((bt[i]<<8 | bt[i+1]))))
        idx=idx+1;


    #print the values
    print(','.join(str(item) for item in out))
    file_object.write(','.join(str(item) for item in out)+'\n')



if __name__ == '__main__':



    b=  b'\xa0\x02vBy\x16\x103\x166V\x02J(\x12n\x19/\x0f9:pw\x0c~)\x03M\x18Gn\x08RG\x06|ZX\x7ftzs1\x06]i' \
+ b"\xa0\x06t\x07OT]Wh\x00i=v\x19\x17T\x19\x0cr\nN'y3\x11\t]h\x1a\x13F\x1a7\x1f%[eX\x0c\x7fx\x02+\x07-\x08" \
+ b'\xa0\rJ,\x10y\x1aFX.\x13e63J>3ou#O0s\x1c93m"3wo#$z,j\x13H!(\x07\x03-\x08FM' \
+ b'\xa0\t-Ry]^\x1fv&<\n>\t7x\tq\x06\x18eO*(q\x19D/\x08\n\\)a \x16IWv^s\x05r \t*T' \
+ b'\xa0\x02g\nY\x1f\x103\x03\x0fFFQ)\x0f\r\x19=39\x17\x05hv*)\x17`\x19N\x07\x08\x00b\x06\x179XZmzw;\x06_f' \
+ b'\xa0\x02t&MT\\W^}i%z\x19<d\x1a!\x02\n\x1d+id0\x19\x0ed\x1a\x15C\x1a\\)%fdY\x06\x03x\x04/\x07/\x0c' \
+ b'\xa0\rJ\x0c\x10y\x1cFoG\x13\\\x163R53du#\r2s\x7f93h\x143[g#,w,h&H#0w~*\x08FM' \
+ b'\xa0\t-\x13i]^\x1fbQ<\x17+\t\x14`\x089r\x187R+*\x00\x19)%\x08eP)G\x1b\x16Ccvs~\x05v\x11\t+W' \
+ b'\xa0\x02\x7ff{V\x103D1Fz"\x18cY\x19\x0c\x02)uWg\x0fr\x19t2\x18(W\x08K1\x06j{X\x17szu8\x06]i' \
+ b'\xa0\x06v\x06M\x14]Wn>iq\x07\x194g\x1a%\x18\n=Cy\x13Q\x19By\x1aL`\x1a\x7fC%}XXttx\x01,\x07.\x08' \
+ b"\xa0\x0fK,\x02y\x1cF\x03o\x13/I3s>3wy#S+s#z3$,C\x13t#j|,k'H0'w}.\x08FQ" \
+ b"\xa0\t}\x12Y\\^\x1fjR<5u\tk%\t\x1c:\x18tq+-[\x19ko\t*\x0b9\x15M\x16pdv}}\x05n\x10\t'X" \
+ b'\xa0\x02\x7f\x07{V\x103\x01\x0fF46\x18cO\x18\x16|)eThU\x1e\x19R;\x18\t[\x08)/\x06arX\x11vzuC\x06^h' \
+ b'\xa0\x02=#_\x14]W;sYmV\tb8\x19oW\nl\x13i0\x11\t\rN\n41\x1a\x16\x0f%GlX}}x\x089\x07/\t' \
+ b'\xa0\rJ\x0c\x10y\x1aFoH\x13J\x163\x0b\x1939L#\x05\x0bsG\x0c3\x14\x003%Q#$U,J:H>)\x07\x02#\x08EV' \
+ b'\xa0\t-ri\\^\x1fI6<\x1f:\t&z\tF\x02\x18dd\x1axf\x19\x1dM\x08t\x7f9\x072\x16^SvB\x7f\x05w\x0f\t)Z' \
+ b'\xa0\x02\x7fF{\x16\x113\x1c\x0eFf/\x18dd\x19\x07\x03){egc^\x19_H\x18\x17p\x08W@\x06oaXs\x00zsF\x06]n' \
+ b'\xa0\x06v&I\x14]We+i6\x18\x19Dl\x1a\x1e\x1a\nPMiw0\x1a\r\x0c\x1a\x18o\x1avH%wOXh}x\n&\x07,\x0f' \
+ b'\xa0\x0fK,\x10y\x1aF-Z\x13(+3Q+3Zq#?&sR03j$3rn#Es,\\8H<"\x07\x05+\x08FZ' \
+ b'\xa0\t}\x1eY\\V\x1fk<<?Y\tZ\x0f\t\x0c7\x18$\x0b+m\x1c\x19]~\t6\x179\x06P\x16lMvXy\x05p~\t(\\' \
+ b'\xa0\x02>g{F\x113a\x18V\x1f\x03\x18F6\x18qe)r;g)G\x19F\x1b\x18\x0f;\x08(\r\x07<$XR\x04zn:\x06\\j' \
+ b'\xa0\x06v&k\x1c]WO[i%\x1b\x19/x\x1a72\n[hi}\\\n{\x1d\x1a4\x03\x1an^%\x0b<Xexx\x06\x1d\x07-\x0c' \
+ b'\xa0\x0bJ,\x10y\x1cG!\x14\x13f[3:43Ie#I\x18s\x19]3J\x183tX#Ub,M>HJ$w\x7f\x1e\x08F]' \
+ b"\xa0\tv\x1e]\\^\x1fb2<o\x15\x19\x01@\t\x18R\x18[,+]T\x19\x7f\x02\t^%9.q\x16\x7f%v0}\x05g\x10\t'\\" \
+ b'\xa0\x06?c{F\x112_JFq"\x18S6\x18mb9\x00?g"f\x19R\x1b\x18\x0f8\x08 \x16\x07=\x18X>\x05zkI\x06\\j' \
+ b'\xa0\x02v\x06i\x1c]WipiL\x18\x19*t\x1a/-\n\\hy4W\x1a\x15\x13\x1a\x0f\x03\x1ame%\x08BX_yx\r\x11\x07,\n' \
+ b'\xa0\x0fK,\x14y\x1aF6E\x13%B3&\x173FN#4\x07sU<2m}3^H#0U,>GHH#\x07\t\x0b\x08Fa' \
+ b"\xa0\t|\x1e[\\^\x1f8 <[q\t[\x1e\t\x17E\x18\x19)+P8\x19 o\t_*9\x0fm\x16z\x1dv\x1fy\x05o\x06\t'V" \
+ b'\xa0\x06=g{F\x112\\XFAt\x18,#\x18[R)01g\x10E\x19=\x06\x18\x110\x08\x0c\x06\x072$Xe\rzkY\x06\\l' \
+ b'\xa0\x06v\x06i\x1d]WR+i+\x0b\x19Nz\x1aQC\nA|y.h\x1a\x19+\x1a[\x16\x1a\x05q%\x1c2XYqx\x00\x15\x07-\x06' \
+ b'\xa0\x0bK,\x10y\x1cGM\x14\x13*>32\x1c3?S#&\x04sVY3A\x103YN#RR,=XHg\x19w\x7f\t\x08D^' \
+ b"\xa0\t~\x1e]V^\x1f+D<\x7f\x07\tt>\t\x1fe\x18(?+hl\x19M\x1c\t@H9'\x00&\x0f v\x1au\x05t\x06\t'W" \
+ b'\xa0\x06=g+G\x112+hFKp\x186#\x18XW)K6g\x1eW\x19*\x0f\x18k3xw\x08\x07/!X+\x06zxW\x06\\i' \
+ b'\xa0\x06f&i\x1c]W\x0f\x1ei\x00\x07\x1a4\x04\x1a:0\n)gizq\x1a\x0e\x18\x1a\x1d\r\x1akd%\x108X6vx\x0f\x16\x07-\x06' \
+ b'\xa0\x0bK \x10y\x1aG "\x13\x10=3\x11#30N#uss\x1fp3M\x043II#/P,@dHp\x1c\x07\x10\x16\x08Da' \
+ b'\xa0\t~^_V^\x1f\x1aZ<`\x1b\ti]\t\x1dy\x18"/+2\x10\x19\x16$\tPY9"\x15&\'0v@y\x05v\x08\t&W' \
+ b'\xa0\x06?Go\x04\x112||F9\x05\x18"\x18\x18HN)\'\x19gcQ\x18\x1fy\x18f&\x08\x07y\x07 Ph\x1e\x15zg[\x06\\k' \
+ b"\xa0\x06gni\x1f]W\x18[YZ5\x1aT\x1b\x1an]\n>\x1aiR\x1f\x1a\x19S\x1aN@\x1a\x13\x18%9\x00X'yx\x05\x0b\x07,\x07" \
+ b'\xa0\x0bK$\x10y\x1aGC$\x13\t43,\x123AI#\x0bvs\x1fJ3J\x043cC#TB,5`Hl&\x07\x07\x1c\x08E^' \
+ b"\xa0\tt\x1e_T^\x1f-<<ie\x19\x18&\t>X\x18,\x16+?R\x19\x1d\x0e\tv49A[&\x055v\x19x\x05t\x15\t'W" \
+ b'\xa0\x06-\x0f\x0fI\x112~CFX\x1d\x18\'c\x18@!("zg\x7fo\x18"Y\x18j~xgKw}]Xv\x0ezlY\x06[m' \
+ b'\xa0\x06f&i\x1c]W/\x19i\x01\x03\x1ai\x0f\x1aN6\nUoiw;\x1a2\x1d\x1aE\x16\x1a|l%\x1d0X2rx\x17\x12\x07,\x04' \
+ b'\xa0\x0bJ$\x14y\x1aGB\x1d\x13u\x173\x1e\x0c3*=#\x11os\x04,2Bl3O<#>8,4mHx\x1f\x07\r\x0f\x08Ff' \
+ b'\xa0\x0bv\x1f_\x06^\x0fiI<O\x03\x19\x13Q\t]\x0b\x18Yj+\x13{\x19&0\tzy9H\x1f&?|vos\x05p\x05\t%W' \
+ b'\xa0\x04-G/\t\x11B\x02nFRG\x18\x00d\x184!)\x0c\x04g|1\x18\x00V\x18X\x01xcNw\x7f`h\x19\x18zci\x06[l' \
+ b"\xa0\x02gNi\x1f]X2\nYrD\x1af.\x1aob\nY!y)7\x1aFT\x1ag@\x1a#\x12%?\rX'mx\x12\x01\x07-\x03" \
+ b'\xa0\x0bJ$\x149\x1aG[j\x13vs3\x16\x1b3"A#\r_sOt2R~3U-#[4,/vX\x11\x1c\x07\x0f\x1c\x08Ek' \
+ b'\xa0\tv_O\x16^\x1f\x0c=<KO\x19Dp\tr\x19\x18zW+[\x13\x19U9\x19Fb9|&&A\x03v{p\x05g\t\t$W' \
+ b'\xa0\x04%\x0e\r\x19\x11B\x1b4FwP\x18{9\x18$|(\x14`gzl\x18}1\x18daxJ*wS\th(\x16zdd\x06Zn' \
+ b'\xa0\x06.\ny\x1fUW\x18\x0bi+%\x1a\x00%*\x05P\x1a \x18y\x15~\x1aWC\x1a ;\x1a\x0e\x08%3\x0fX\x0efx\x12}\x07.\x01' \
+ b'\xa0\x0bK$\x14)\x1aGm\t\x13\x19\x1d3\x16\x023\x15##*PsPJ2<c3_\x1d#5\x19-\x17\x08X!\x13\x07\x0c\x17\x08Cf' \
+ b'\xa0\tv[_V^\x1f\x0f/<(\x1b\x192Z\tV\x00(\x00Q+7\x12\x19?<\t\x7f]9]\x12&=\x01v\x04o\x05o\x12\t%U' \
+ b"\xa0\x04%\x0e\r\t\x11BL$FI'\x18k9\x18\x0fy(\n_gza\x18l9\x18#[xB/wU{h/\x14z_m\x06Zl" \
+ b'\xa0\x06.\ni\x1f\x15Wa\x08iA(\x1a\x1e4*\x14`\x1a!\x19y@g\x1a\\C\x1a_9\x1a4\x16%?\x08X\x05hx\t{\x07-\x7f' \
+ b'\xa0\tK$\x14)\x1aW,\x13\x13_@31\t3-2#4Rso\x1e2Ib3<$#k%-+\x13X,\x1b\x07\x0e\x11\x08Do' \
+ b'\xa0\x0b&so\x0f^\x0f]!<fV\x1ax(\x198U)02+\x12-\x19e\x7f\x19\x1eN9\x11s&\x1c0v4i\x05s\x05\t!W' \
+ b'\xa0\x04%*\r\x19\x11B\x10\x19FP\x0f\x18e\x1c\x18\x08](lXg,W\x18I)\x18\x08Qx$\x17w=\x12h<!zbu\x06Yn' \
+ b'\xa0\x04.Ni\x0f\x1dWj*i\x0c&\x1a\x109*\x03b\nr.y<$\x1a0Y\x1a F\x1a\x01\x1b%?uX\x03mx\x0f\x06\x07.{' \
+ b'\xa0\x0b\x1b \x04)\x1aGf\x17\x13R42\n\x7f3y\x19#s:sZU2\x0e]3\x7f\x04#\x1a\x0e-\r\x16X?\x13\x07\r\x08\x08Cv' \
+ b'\xa0\x0bfS\x7f\x0e^\x0f2"<[V\x1aO(\tsF)\x06\x19+(D\x192s\tj&9Ui&\x00&v5p\x05n\x0f\t![' \
+ b"\xa0\x04%\n\r\x19\x11B.\x1fFt\x0f\x18Y6\x18\x00{(R^gCk\x18O1\x18\x0b`x\x19*wS\th:'z[i\x06Zr" \
+ b'\xa0\x06.oyNUW\x1e\x07i1,\x1a\x1bY*$\x06\nc5i{\r\x1aIb\x1a\r^\x1au0%\\cHKdx\x14w\x07.\x04' \
+ b'\xa0\t\x13 4"\x1aW\x1d\x1a\x13?\x1f2kr3q\x0e#N*s"\x1c2\x0bD2=z"v~-z"X/\x11\x07\x12\x03\x08Dx' \
+ b'\xa0\x0bv_o\x06^\x0fv\x13<F0\x19<u\t] \x18vx++\x15\x19EJ\x19,\x009I9&MSv#l\x05o\x04\t%U' \
+ b'\xa0\x00w\n\t\x1a\x11BtyEbs\x17+i\x08O0(C$ge \x186v\x18\x07\x1fwnlw\x037hV\x1czXj\x06Yt' \
+ b"\xa0\x06.Ny_\x15W?\x19i\x13'\x1a\x0eC*\x0fh\ny!y+\x02\x1aZO\x1a\x04H\x1a\n\x16%>\x03Hwax\x0fn\x07.~" \
+ b"\xa0\tK \x14)RW#,\x13+\x1c3\x04\x083\x04%#l8sA52'P3;\x0f#@\x10-\x19\x1aXD\x11\x07\x0f{\x08D\x04" \
+ b'\xa0\x0b&Wo\x0f^\x0f1\x1a<\x118\x1aa(\x19\x1cT\x19t#+-E\x19Wp\x19:<9\x15l&\x12\x19v\x04q\x05g\x10\t"b' \
+ b"\xa0\x04w\n\x1d\x1a\x19B\x11\x04E\x1eZ\x17<{\x08\\=(\x1c:g'2\x183\x06\x08l2w~|w\x1a\x11h)%zY\x04\x06Yv" \
+ b'\xa0\x04.NiO\x1dW{Ti\x021\x1a\x06G*\x04g\nI)y\x11\x11\x1aOW\x1aEJ\x1a\x19\x1b%EvHrdx\x11\x01\x07.\x7f' \
+ b'\xa0\t\x12 4)\x12W)}#\x00O2s{3n\t#o$stB2/X2!y#\\\x00-\n6Xk\x0b\x07\x16w\x08Ew' \
+ b'\xa0\x0b&Si\r^\x0fZ]<u|\x1avD\x199])L7+ph*\x01(\x19MM:E\x01&+0v@e\x05o\x03\t"V' \
+ b'\xa0\x04t*\r\x1b\x19Bd8V\x14\n\x17\x1dl\x08T\'(A+g\tU\x18!\x04\x18\x08"w\row\t*hr\'z\\\x01\x06Yp' \
+ b'\xa0\x06.KyO\x15W\x10Yi.I\x1a,p*<\r\x1a\x10MydS\x1aP~\x1aLr\x1ai=%iWHg\\x\x0en\x07,x' \
+ b"\xa0\t\x1b \x14)\x1aW'[\x13x'2\x06z3|\x18#l3s}L2\nP3V\x06#l\x05-\x0f'XY\n\x07\x13\x02\x08F|" \
+ b'\xa0\x0b.Sy_^\x0f\x19\x1e<h^\x1a\x192\x19=Z)7E+vM*\x06\x0b\x19\x01R96|%\x1b|ftf\x05p\n\t!O' \
+ b'\xa0\x00wG;R\x11BO}E}W\x17\x0f8\x07#w\x18}~f9\x7f\x08\x7fM\x07ZtwI>wXKh\x07*zaz\x06Yp' \
+ b'\xa0\x04.oiN\x15WS=i$V\x1a${*1\x0f\neVii>\x1ahu\x1axw\x1af=%hOHc_x\x1cj\x07.p' \
+ b'\xa0\t\x13 4c\x12W\x17R\x13jQ2kr3`\x02#p\x17sCX22<2/s"\x11l-zMX\x06\x08\x07\x15s\x08E}' \
+ b'\xa0\t,\x12i\r^\x0fz#=>\x0e\x1a$k\x1aA\t)f`,i\n*Q)\x19}w:!\x18&:\x19v\x0fb\x05m\x07\t Q' \
+ b'\xa0\x00~#9R\x11B>mU\x0f]\x07w9\x07\x12r(\x15{g\r/\x18\nI\x07Bqw49wKQh\r1zZ|\x06Xo' \
+ b'\xa0\x04.KkO\x15W\\1i8:\x1aCi*(\x04\x1a)?yKD\x1a(\\\x1az^\x1a\x1a4%Q]HW^x\x15k\x07/r' \
+ b'\xa0\t\x1a\x1d4cRW<k#\r22\x01n2Xy"\t\x7f\x03\x01K2<)2\x19a"<k-rLX\x01\r\x07\x14{\x08E\x01' \
+ b'\xa0\x0b<\x12I]V\x0f\x00R=D\x10\x1a4\x07\x1aY\x1b)sb,_\x14*=1\x1al\x02:H=%Tkfoi\x05k\x7f\t R' \
+ b'\xa0\x04~\x03;R\x19Bj+U\x12X\x07o+\x07\x0bb(\rcg\x7f\x03\x08v8\x07E^w5/wCah\x1f6zS\x03\x06Wy' \
+ b'\xa0\x04>K_O\x15WQJi\x16O\x1a@\x0f*_,\x1a\x0ekyZ;\x1a\x11\x10\x1b\x1e\x08\x1a$T%x*H\x1a[x\x16b\x07/s' \
+ b'\xa0\t\x03\x104c\x12W/m\x13\x7f63s\x013k\x12#P\x10\x03\x17C2/G2*h"<e-xLX\n\x10\x07\x18v\x08Dz' \
+ b'\xa0\t.\x12iMV\x0f\x7f\x10<6v\x1aHo\x1aU\x19)I`,}\x00*D7\x19on:9\x14&D\x04fri\x05lt\t E' \
+ b'\xa0\x00>G;RYB}aU\x1f3\x07q\x13\x07\x7fU\x18Xbfy\x7f\x08\\3\x07&Uw\x19\x1ew2`h15zW\x02\x06X\x05' \
+ b'\xa0\x06?O}OUW\x1fei\x0e^\x1aW\x02*G\x1d\nf_ySS\x1aY\x00\x1a\x00{\x1a/F%pIHeZx\x17h\x070Z' \
+ b'\xa0\r\x12\x194c\x12Ww\x12#xV2~Z2Xp"c|\x03\x07H2\x12+2.Q"]X-fgX0\x00\x07\x19s\tDC' \
+ b"\xa0\x0b,\x12y]^\x0f\x05'=z\x17\x1a6j\x1aY\x0e){d,U\x01*D7\x19$h:P*%G}fug\x05j\x01\n\x1f|" \
+ b'\xa0\x02~C;RYB\x00]U(?\x07j\x1a\x07\x07\\(\x02`fRt\x08p2\x07iRw<#w9th&:z[\x07\x07Xz' \
+ b'\xa0\x04?k]GUW3\x04Y\x7fV\x1aN%*d4\x1a\x1dtiw4\x1a_\x14\x1bj\x14\x1a)`5\x0b"H\x10[x\x19P\x08/\'' \
+ b'\xa0\t\x12)6cZW*=#\x02#2RL2Kf"Hts+\x0e"]\x1b29R"%V-cZX\x1b\x0e\x07\x16\x02\x08G\x1c' \
+ b'\xa0\t,\x1eY]^\x0f#\x0b=:%\x1aPx\x1a}!)\x08\x08,A\x07* L\x1a/\x14:GL%n9f*d\x05h\x05\x08\x1f3' \
+ b'\xa0\x00.G?@\x19Bp]U)5\x07B`\x07N\x1d\x18]*fbJ\x07&y\x07\x10\x1ew\x00nx\x01\x0chF4zS\x13\x05W\x1a' \
+ b'\xa0\x04>K]G\x15WNNiD\x7f\x1a>&*V7\x1a\x03ey.U\x1aX\x01\x1b\x11\x11\x1a!Z5\x00)H,Qx\x18Y\x06/\x0c' \
+ b'\xa0\r\x12)6c\x12Wj\x08#$F2?Q2Jd"6esTB"_\x132\rM"\x1dJ-TlX@\x03\x07\x1eg\x07E\x7f' \
+ b'\xa0\t$\x12I]\x1e\x0fS,=\x1d\x08\x1ayn\x1aj\x1f)Oh,\x1e\n*\x077\x1av\n:+/%N]fZf\x05l\x10\t \x1b' \
+ b"\xa0\x02/G?A\x19B\x17~E{\x19\x07\x17b\x07U#\x18%'f;H\x07\x1ay\x07\x07\x1fgtnx\x06\x14hs7zR\x01\x06X\x0b" \
+ b'\xa0\x00~K_G\x15Xu\x1ei5~\x1aP-*\x08I\x1a\x1e{yhp\x1a\x10\x11\x1bB\x16\x1aXi5\x0e/HQOx\x1fM\x0711' \
+ b'\xa0\x0f\x12\x194bRW\x1bH#8i2pc2mh"of\x030p20 2OG2\rS-g{X^\x06\x07\x15d\x08F\x02' \
+ b'\xa0\tt\x1e[VV\x0f_"=\x0fL*\x17F\x1aTU)C8,~^*)w\x1as>J>\x065\x1c.f.f\x05hv\t\x1cN' \
+ b'\xa0\x02/G?HYB\x148Ek\x14\x076\\\x07Y\x0c\x18U+fXU\x07N\x7f\x07$ w\x04ph{\x06hDCzP\x12\x06X\x04' \
+ b"\xa0\x04>K_G\x15WT2i\x1bk\x1aP6*gD\x1a\x13}y'N\x1a\t\x15\x1b,\x14\x1a\x18\\5\x08*H*Sx\x15W\x072x" \
+ b'\xa0\r\x12)6sRWn7#0H2F_2*`"M[st8"x\'2\x17;"5;-U\x02X]\x03\x07\x19`\x08C"' \
+ b'\xa0\t<\x1e]UV\x0fG9=\x1b8\x1aR2\x1a{F)2),@1*I\x02\x1aN-:p_5\x02EfBc\x05j|\t\x1dl' \
+ b"\xa0\x02'F\x0f\tYB<fEr\x16\x07}D\x07\x17p\x18%\x0bf!-\x07\x01h\x07k}gQPh[\x1dx\x04DzM\x17\x06X\x12" \
+ b'\xa0\x04=C_UUW?jZL\t\x1aXB*pW\x1b\x0c\x1cy\x0ey\x1a\x0f;\x1b\x194\x1b\x11\r4)jH\x11Lx\x19F\x070\x05' \
+ b'\xa0\r\x13)&cRWx)\x13KL22[2 [",Ys9<"u(2zG"\x0eK-ccXY\x01\x07\x1b_\x08G7' \
+ b'\xa0\t}\x1e]GV\x0f\x10/-(e\x1a]o\x1a\x08z)\x15T,\x1dX*a)\x1ado:e"5Kvf\x1c`\x05dz\t\x1dn' \
+ b'\xa0\x02/G?IYB\x057EB\x11\x07\nK\x07\x17\x01\x18!\x1bf3:\x07\x14s\x07\x0e\x14gS_hh\x0eh\x7f>zI\x05\x06V\x1e' \
+ b'\xa0\x04>OYOUW\\\x05i\x17K\x1aK\x1a*b(\n|by>0\x1am~\x1b1\x04\x1a%H%k+H4Vx\x18L\x073\t' \
+ b'\xa0\x0b\x12\x190sSW\x1bu#]$2N-261";+\x03\n\x082\x05n2\x1b\x14"W\x12-\'\x15Wtx\x07 U\x08DD' \
+ b'\xa0\rt\x1e[TV\x0e!o=,**\x14A\x1aPW)D1,L\x1e* u\x1an=J*p5\x11\x17f\x0e\\\x05fs\t\x1dz' \
+ b"\xa0\x02$N\x0f\tYBA1U\x13i\x07r\x19\x07\nB\x17\x15_f'\x03\x07\n7\x07ySgD&h0>x\x1bCzH\x14\x06X*" \
+ b"\xa0\x04|COUUWXSj/\x15\x1at`*\x1e^\x1bK\x1by?e\x1a?,+ <\x1b'\x014,vH\x01Ex\x14L\x071\x14" \
+ b'\xa0\x0f\x12)4sSW/)#JL2GO24R"PEsz%2\n\x072O4"2--N\x07Wk{\x07\x1eR\x08GR' \
+ b'\xa0\tt\x1fOV\x1e\x0f.\x15=\x15j*\x0b\x7f\x1aK\x11)D[,QC*\x18+*+wJ\x10 5L\x04f\t^\x05f\x04\n\x1c\x01' \
+ b"\xa0\x02-\x0e\x1fIYB(&Ehm\x07\x0f5\x07\x1bg\x17\x10|f(u\x07\rW\x07\x1fqgG@hR#hyMzK'\x06X5" \
+ b'\xa0\x04wCOGUW*AYko*\x13S*\x1fg\x1b5\x12y)S\x1a+%+\x04/\x1a {5\x1e\nH\x15Kx\x1eO\x074\x19' \
+ b'\xa0\x0f\x12\x1923SW\x1b>#)D2:M2\x1dE"8/\x03\x16\x1d"z{2,\x15"+"-D\x1cg\n{\x07#]\x08Fb' \
+ b'\xa0\n|\x1fO\x07\x1e\x7fz\x17=\x05a\x1afx\x1a,\x06)@],kJ*\x11\x1a*\x05b:y25Mwf}]\x05g\x06\n\x1b\x10' \
+ b'\xa0\x02T\n\t\x03YBc-U\x1eT\x06\x15mw1\x0e\x17Q fo8\x07:y\x07@\x10f\x0bsh\x7fuxcSzG\x16\x06WC' \
+ b'\xa0\x04~C]GUW3Pi>|\x1a9D*\x08T\x1b\t\x01yS"\x1aX\x0f\x1b@\x18\x1a/z5\x1a\x1cH)Jx\x1cJ\x074,' \
+ b'\xa0\x0f\x13\x1963SW\x16V#\nf2Eb2<Y"U.\x03!\x08"\\\x0f2\x1b&"y/-O/g\x12u\x07&Y\x08Es' \
+ b'\xa0\t$;O\x16\x16\x0f\x0f$=Kg+R\x15*\x13#9\x10Y,\tC*;<*LxJp85P\x0ffj_\x05a~\n\x19 ' \
+ b"\xa0\x02D\n\r\tYBW7UGM\x07d\x15w}A\x17zUfVh\x07o6\x07eLgP%h'Kx&RzA\x1b\x06XS" \
+ b"\xa0\x04~G]EUWLxi\x14o\x1aeA*\th\x0bw\x17y=`\x1aw'\x1b+*\x1b'\n5\x1f\x14H\x1fFx\x16=\x076:" \
+ b'\xa0\x0b\x12\x19&3\x13X5\x04#jT2-12\t9"*-\x03$."]\x042z\x10"=%-6\x1dg\x1dl\x07\x1eM\tE\x0e' \
+ b'\xa0\ne\x17\x7f\x06\x16\x7f>1-b~+\x06\t\x1a-\x1e*^\x10,Qh*\x18S\x1aw\x04J\x17[5bJf\\^\x05bz\n\x1a8' \
+ b'\xa0\x02D\n\r\tYBI>U\x1ac\x078\x01w:$\x17n<fXc\x07X\x17\x07H.g\x1f\x11h\x14WxJXzD \x06Yr' \
+ b"\xa0\x06tBO\x15UGz7j'\n*\x00t+F\x17\x1b@:yam\x1aZ<+ T\x1bG24HhHoJx\x14;\x075P" \
+ b'\xa0\x0f\x12\x1903\x13W\x1d/#b/2/22\x1f2":\x1c\x03+\x0e2\x1ce22\x02"P\x14-$0g\x15r\x07\x1f]\tG(' \
+ b'\xa0\x0ee\x13o\x06\x1e~B\x7f-bg+K\x1f\x1aw!:\x02\x0f,_g*oA*`\x01JDV5\\Tf0]\x05^\x03\n\x1aM' \
+ b'\xa0\x02T\x0b\t\x03\x19BS*U\x075\x06,bv%|\x17O\x15f7;\x07Hh\x07K\x04f\x0eghkvxZUzD\x1c\x07X\x03' \
+ b'\xa0\x04uC_UUW\x1dzZ\\\r**c*5y\x1b_*yFd\x1ab"\x1b{;\x1b7\x1746xH\nAx 5\x074\\' \
+ b'\xa0\x0b\x12\x19\x101\x13XU\t#?@2U 2\x12\x1f"\\\x08\x03H\x142\x1eX2(w"S|-\x1bLg>l\x07$E\tG4' \
+ b'\xa0\ne;o\x06\x1e\x7fd\x1d-Zf+a\x06\x1a|\x149\x1av,9k*e8*)\x03JGH5Wofg^\x05d\x03\n\x1aT' \
+ b'\xa0\x02U*\r\x0b\x19B}\x1dEt<\x06SkwV\x0f\x17r*f.U\x07c\x04\x07E#f$zh\x02pxD]z@+\x07W\x05' \
+ b'\xa0\x04uc_EUW\x1aKZo\x01*\x0bb*)y\x1b )y"v\x1a0+\x1b@A\x1b/\x135,\x03H\x02Cx\x18D\x075^' \
+ b'\xa0\x0f\x12\x19"3\x13W4m#:a2\x1f,2\x05&""\x11\x03GI"ek2W\x06"L\x07--3g=m\x07 I\tI<' \
+ b'\xa0\ngWo\x0c\x1e\x7f\x02\x02-.\x16+8/\x1a\x7fK*|=,g(*Nt*\x10@KH\x035\x18\x14f\x17Z\x05^\x02\n\x19P' \
+ b'\xa0\x00\x19\x0b1RYR\x02"Ek.vrSv}o\x17\x0c\x00f79\x07\x11_v~~f|Xhh\tx\x06[zC/\x06Y~' \
+ b'\xa0\x00}\x06_UUX\n\x06ZK\x14\x1att+(\x15\x0bt>z3\x0f\x1a\x110\x1b6I\x1b4.4EoH\t:x!3\x077P' \
+ b'\xa0\x0bR)\x023\x13X;"#\tV2\x10*"v\x1f"\x05\x13svC"7_2R{"7\x0c-!7gFo\x07&>\tI%' \
+ b'\xa0\x08fSo\x0c\x1e\x7fw\x17-y\x10+8C\x1abC:\x04I,\x1b#*\x1cr*"2K<\n5\r\'f\x15\\\x05a\x0b\n\x1a/' \
+ b'\xa0\x02T\x0b\t\x03YB`\x17U\x00*\x06\rav\x05|\x17J\x15f 2\x07\x19k\x07)\x08f\x07jhq}xs]zA>\x06Z^' \
+ b'\xa0\x04vBO\x15UW\x13Oi/v*(k+0\x16\x1bs9ygV\x1aN4+\x1dL\x1bX*4<kHr;x\x1f,\x078\x19' \
+ b'\xa0\x0fS\x11\x101\x13Wjv#\x0eI2( "v!"]t\x03C\x042\x08g2$t"{~-+HgRa\x07*B\tI\x02' \
+ b"\xa0\x0e'Ri\r\x16~\x12m-0\x1c+Pb+\x1d\x05:IZ,X\x0f;\x03'*I[K\x00*5L\x14f\x1bT\x05\\z\n\x17\x01" \
+ b'\xa0\x04Y\x0b)RYQ\rzEU\x1dvzSv\x01q\x17$\x03f\x17\x1e\x07"e\x06\x11{f|Uhj\rx\x07_z?/\x06Y2' \
+ b'\xa0\x06eBO\x15\x15Gv#Z_\r+\x0c\x03+J\x1f\x1baOy\x1e~\x1a>B+\x0b`\x1b 74VWHX=x\x1f$\x077q' \
+ b'\xa0\x0fR\x11\x021SW]`#m=2\x0c\x0f"k\x02"2n\x03\x053"RK2}e"+f-\x12Ygbo\x07#D\x08KR' \
+ b"\xa0\x0c'Sk\x0c^~wg-5\x14+b_*\x1fc:8U,6@+l\x11*SZK]\x1955\x1ef\x11T\x05^\x03\t\x19Z" \
+ b'\xa0\x02\x19#1RYBo\x0cEg\x0cv{Pv|f\x17\x1fwVz(\x07\x11UvwqfsKha\x19x\x0f^z:&\x06Z\x0b' \
+ b'\xa0\x06e\x02_U\x15Gs~Zq\x1f+\x12\x01+N%\x1b\x1cKz\x15\x07\x1a\x1c:\x1b\x13V\x1b274SeH\x02<x\x1c0\x079S' \
+ b'\xa0\x0b\x12\x11\x021SXi6#zl2\x1d\x1d2\t\x1f")s\x03\rK"`P2=c"jh-&ogxe\x07$1\x08J;' \
+ b"\xa0\n'ri\rV\x7f$\x04-u/+\x7fi+X\x11:Sd,\rR;\x19\x12*^TK\x18 5J@f0Z\x05]w\t\x17G" \
+ b'\xa0\x00T\x03)S\x19R;\x0cUR\x15\x06\x00Mv\x0bo\x173vfo1\x07#T\x06\x11nf\x10Fhd&x\x1fiz:.\x06\\\x7f' \
+ b'\xa0\x04d"MU\x15WHdjH\x1a+I\n+d,\x1boLz\x1a\x06*\x02H+*c\x1bc?4SfH\x06@x 2\x078A' \
+ b'\xa0\t\x13\x11\x100Sh1$#,i2L+2\x12\x1c"Vn\x03g(2\x1dl2/r2\x03~-2Xg~i\x07&9\x08J<' \
+ b'\xa0\x0e?Ry\rV~\x1e\\-06+\x1e\x07+e\x1d:_k,C5;0H*\x1dzK\x1fJ5g\x07f\x07T\x05\\l\t\x17A' \
+ b'\xa0\x04\x18\x072R\x19Q%|U\x04\x13vS6v[Q\x07nKf\x10\x08wvAv\x7fUfo1hMBx"fz<8\x06Z~' \
+ b'\xa0\x06e\x02_\x15\x15G[jZ_)+1\x03+iB\x1b(Yz(\r\x1a;V\x1bis\x1bTK4bYH^8x\x1f(\x079A' \
+ b'\xa0\x0bR\x11\x021SX`\x19#oc2\x17\x04"d\n"\x1bf\x03\x135"\\Z2Rc"]k-\x18Zgjc\x07,0\x08L;' \
+ b"\xa0\x0c'ZI]^~74-\x18*+~s+:':X\x12,=`;#V+c\x0cK\x08W4dqVPP\x05^\r\t\x18=" \
+ b'\xa0\x04\x1bC2RYQ6LDzbv9 v7?\x17\t:e%ewn"vj>fW\x1ch9^x2bz:6\x06[\x00' \
+ b'\xa0\x06eBO\x15\x15Gv1ZB\x17+%\x1c+aO\x1bPiyB~\x1abN+\rp\x1b>Y4pVHc;x\x1e)\x07:<' \
+ b'\xa0\tR\x11\x103Sh\r\r#[r22+"\x7f\'"Gf\x03?"2\x16_2\x08h"~\x04-0Tg{a\x07(8\x08JC' \
+ b'\xa0\x0c>^Y]V~b<-dF+,#+nN:\x03\x1e,bT;bg+!\x13K4z4\x7fSVUP\x05W~\t\x16C' \
+ b'\xa0\x04\x1bG1RYQRYDXqvW,vRP\x07uKe@n\x07\x054v{Jfk+hBHx?gz7*\x06[\x03' \
+ b'\xa0\x04eBO\x15\x15WJ2Z\\\x17+:\x08+i<\x1b\x1bXyR}\x1a_J+"k\x1bME4Z\\Hd9x".\x07;;' \
+ b'\xa0\tS\x11\x12pShG\x0c#\x03h2"\x10"r\x07"\x0f@\x03; "iH2\x1cI2\x0eP-\ryg\x17g\x07&3\x08KF' \
+ b'\xa0\x0c?ZY]V~^1-\x103+\x17\x18+mD:g\x0f,CV;0e+1\x11KB]4shVNW\x05Z\x7f\t\x17A' \
+ b'\xa0\x04\x1aG2RYQUYT&Wv(\x1bv>@\x07f3e\x0fLwV\x1eve4f_\x15h2}xKoz85\x06\\\x01' \
+ b'\xa0\x06vB_\x15\x15Gx=i\x11p*\x15y+hE\x1bVcyV`\x1aHN\x1blp\x1b1T4VgHo6x\x1f5\x07<;' \
+ b'\xa0\tS\x11\x02aSh5-#PL2!\x00"n\x00"DB\x03J\x05"hE2tJ"]T.\x0f\x08g&a\x07(9\x08LJ' \
+ b'\xa0\x0c?^YU^~X7-M)+O-+}T:\x19),g?;En+A*K.oD\riV?Q\x05[\x01\t\x16C' \
+ b'\xa0\x04\x1aG2RYQYXT\x18AvN*vHK\x07wCe2EwX,vuHfj\x1ehEexYsz60\x06[\x07' \
+ b'\xa0\x06gJo\x15\x15GaRYm{+?\x1e+xe\x1b{\x06yvu\x1aIl+9\x0b\x1b^f4lKHE8x$&\x07<<' \
+ b'\xa0\tR\x10\x02qSh#B#pe2\n\t"Kz";K\x03M#"HH2n;"qS-\x08ng\r[\x072*\x08MP' \
+ b'\xa0\x0c>ZIW^~\x15c-TE+ 8+bf:zR,Zy;5}+q.K+\tD\x12<V\x1aQ\x05_\x08\t\x16C' \
+ b'\xa0\x04\x1aG2RYQU{T\x0cJv\x1e\x15v)1\x071/e\x18AwC\x0cv&&fL\x0bh-vxavz8N\x06[\t' \
+ b'\xa0\x04$BO\x15\x15W\x0crj\x03\x05+U\x10;\rJ\x1b oyN}\x1alT+\x00y\x1bTS4d[Hg9x!1\x07<;' \
+ b'\xa0\tS\x10\x10`ShOG#Hm2H\r"|~"(@\x03v52\nJ2\x13@2\x15D.\x13\x0bg-]\x07/*\x08KJ' \
+ b'\xa0\x0c\x7f\x1e]U^~*--\x07;+b:+\x18n:\x13?-s\x02;\\\x05+?-KPoD\x1deVDR\x05W\n\t\x15A' \
+ b'\xa0\x04\nG6 YQt\\T\x135v\x03sv\x01\x15\x070\x07e8<v\x17ov/\x0cfK`i\x11%\x08\x08pz0>\x06\\\x06' \
+ b'\xa0\x06g\x02\x7f\x15\x15GE*YBv+0\x15+\x7fX\x1b:|z7\x0f\x1a\x16[\x1bj\x05\x1bLZ4iQHJ/x\x1e#\x07>5' \
+ b'\xa0\tB\x10\x02pSh\x1b5#ju2\x17~"Tr"(A\x03\x14E"@<2a92\x03W-\x10qg\x1fW\x070*\x08NT' \
+ b'\xa0\x0c~\x1e]W^~\x0e\x0b-ER+Y@+\x12x:3_-N";R\x16+UEKa\x1fD3,V\x1bQ\x05V\x02\t\x149' \
+ b'\xa0\x04\x1bG2BYQeADS7v\x0e\x06v\x1e-\x077!e/]w:\x05vK!fU\x00i%\x03xtzz74\x06\\\x01' \
+ b'\xa0\x06\'"O\x15\x15G~oYD{+\x1b\x0b;\x02L\x1b!gz\x1f\x13\x1aPS+\x01u\x1bEN4YmHy5x&\x18\x07>(' \
+ b'\xa0\rC\x10\x02`ShP\t#\x1aj2\x18q"\\e"*1\x03y>"p82\x7f62\x12;.\x01\x0bg9R\x07-+\x08NP' \
+ b'\xa0\x0c~\x1e]\x17^~\x00J-\\8+V<+\x0ft:&h-O\x16;Z\x1b+L_KT\x17D-!VrM\x05Q\x0f\t\x158' \
+ b"\xa0\x02CG6@YR \x02D\x7f\x16vl^ft\n\x07\r\x07e\x06\x14v\x03cv$\x0ef'bi\x10*xy{z,:\x06^\x02" \
+ b'\xa0\x02gJo\x15\x15Hz\x0fYT|+\x08\x16+vl\x1b,\x08y/p\x1a*V+\x0f\x13\x1b\x1fs4sQHP3x&\r\x07@(' \
+ b'\xa0\rY\x10\x12pShf\r$]\x00"w\x04"Sl"3+\x03p\x13"Z,2\x0022\x0bS-\x0b|g:R\x072*\x08NP' \
+ b'\xa0\x0c~\x1f_\x17V~\x04.-g]+W`+6\x04:Uh-n\x03;\n\x1c+peKm7D>\x0fVwL\x05Z\x7f\t\x156' \
+ b'\xa0\x06\nG6 XQ6fT63v\x02jv\x14\x05\x074\x05e9-v/mv:\x0efGfi\x0c\x1d\t\x16\x00z/A\x06^\x03' \




    stream=io.BytesIO(b);
    print("START DECODING")
    while 1:
            #remove marker
            stream.read(1)

            #decode 45byte data
            try:
                decode_data(stream.read(45))
            except:
                print("END DECODING")
                file_object.close()
                break;

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

So, upgrade.

When I connect to the EMG port I get a nice signal, but I don't get anything else meaningful-similar to the official EEGsw on other channels. :cry: There must be something about bit order that I am not doing well.

What I am very confident of is that 45bytes carry samples of all 26 channels. I measure 10 seconds and I received 460bytes (45 bytes +1byte marker)x10sec, which tells me that the data must be in 45bytes, and in particular 44B+4bits as the first four bits of the first byte are not used (are always zeros)


How do I know that there are 26channels? If I load EDF generated by their software in Matlab/EEGLAB I can see 26 channels, on the other hand the math gives exactly the same results, so: 44.5Bytes x 8 - 44unused zeros at the beginning = 312bits in total. 312bits/12bit per channel=26channels in total!

Now, how they are distributed, I still didn't figure out. For me it is a mystery how I get the ECG only on the last channel, but not on the others. If somebody has some idea how I might decode the channel info, let me know.

Thomas
Posts: 210
Joined: Wed Mar 04, 2020 3:38 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by Thomas »

Hi A1eks,

Thanks for the upgrades.

I see the comment about the byte swapping, saying the first byte is the least significant one and the second the most significant one.
I am a bit confused because just bellow, this is not what the code does and another comment (which comments what the code actually does) is contradictory with the byte swapping: First byte represents the 12 most significant bits and the second one represents the 4 least significant bits.

Also, I think the data for each channel comes together, and so the byte swapping (if needed) should be the last step after extracting the bits of data.

The script you shared starts by swapping all the bytes of the buffer. I don't think this is correct.

Note how it is done in the KT88 1600 driver: Looping through each channel, it first reads 2 bytes for the channel, and then arrange them to represent the data. Swapping the whole frame from the start is very likely shuffling the data.

When you spy on the data using the provided EEG software, can you change the amount of channels that you want from the amplifier ?
This would be an interesting way to see if the amount of data still follows the same pattern.

Have you tried contacting Contec to learn more about the formatting of their data ?

Hope this helps a bit, let us know how you get on.

Cheers,
Thomas

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

Hi Thomas,

You are welcome, thank you for your suggestions!
Thomas wrote:
Fri Oct 29, 2021 10:09 am
Also, I think the data for each channel comes together, and so the byte swapping (if needed) should be the last step after extracting the bits of data.

The script you shared starts by swapping all the bytes of the buffer. I don't think this is correct.
So, you suggest loading the data as it is, and then swapping bits like thins:
ch1 = [0000 1111][1111 1111] ---> [0000 1111][1111 1111] :?:
This is, at leas, what was done in the code, if I understood correctly:

Code: Select all

l_ui16Value = (((uint16)l_pBytesRead[0]) << 4) | (l_pBytesRead[1] & 0xF);
Thomas wrote:
Fri Oct 29, 2021 10:09 am
Note how it is done in the KT88 1600 driver: Looping through each channel, it first reads 2 bytes for the channel, and then arrange them to represent the data. Swapping the whole frame from the start is very likely shuffling the data.
That natural way to do it, but the difference is that each channel is always represented by two bytes, and in the case of KT88 that varies, so I would have to read sometimes 3 bytes, and then shift them differently for each channel. For that reason, I did read all 45bytes at once and tried to manipulate them.
Thomas wrote:
Fri Oct 29, 2021 10:09 am
When you spy on the data using the provided EEG software, can you change the amount of channels that you want from the amplifier ?
This would be an interesting way to see if the amount of data still follows the same pattern.
No, unfortunately, you can switch off the channel in the software, but that is just not visualizing it, the amplifier still sends all the data.
Thomas wrote:
Fri Oct 29, 2021 10:09 am
Have you tried contacting Contec to learn more about the formatting of their data ?
Yes, I have contacted them and waiting for their response, but I don't expect much. Maybe I am wrong.
Thomas wrote:
Fri Oct 29, 2021 10:09 am
Hope this helps a bit, let us know how you get on.
Thanks. For now, I tried to disassemble the exe and to find what exactly is doing.

Here is part of the assembly code responsible for the bit decoding
https://github.com/miladinovic/KT88-160 ... n/kt88.asm

I cleaned it and try to make some comments.

So, what I figure out is that the code reads 45 bytes (there is a loop 45 time that evokes ReadFile function to read com port and reads byte per byte)

routine
seventh_function_read_serial_45B:

then it compares the read byte to the markers A0h or B0h just to ensure that it is not contained in the next 45byte sequence.

Then for each byte read it calls routine

fift_funnction_process_byte from which decode_bytes is then called to perform further processing.

second_proc_step routine loops to accumulate several samples then first continue and show them

I have a feeling that third_proc_step or process_load_data do the thing of bit decoding, maybe I am wrong.

Best,
Aleks

Thomas
Posts: 210
Joined: Wed Mar 04, 2020 3:38 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by Thomas »

Hi Aleks,
So, you suggest loading the data as it is, and then swapping bits like thins:
ch1 = [0000 1111][1111 1111] ---> [0000 1111][1111 1111] :?:
This is, at leas, what was done in the code, if I understood correctly:

Code: Select all

l_ui16Value = (((uint16)l_pBytesRead[0]) << 4) | (l_pBytesRead[1] & 0xF);
Yes, that's what I think, maybe not on two bytes as the format seems different, but the idea would be similar.
Also I think the result to this code shifting is slightly different:
ch1 = [0000 1111][1111 1111] ---> [0000 0000][1111 1111]
1. ((uint16)l_pBytesRead[0]) = 0000 0000 0000 1111
2. ((uint16)l_pBytesRead[0]) << 4 = 0000 0000 1111 0000
3. (l_pBytesRead[1] & 0xF) = 0000 1111
4. (((uint16)l_pBytesRead[0]) << 4) | (l_pBytesRead[1] & 0xF) = 0000 0000 1111 1111

That natural way to do it, but the difference is that each channel is always represented by two bytes, and in the case of KT88 that varies, so I would have to read sometimes 3 bytes, and then shift them differently for each channel. For that reason, I did read all 45bytes at once and tried to manipulate them.
I agree that with the format you suggests, it would be a pain to do. But still, I believe that swapping bytes on the whole stream without knowing for sure that it is needed will not help.
Here is part of the assembly code responsible for the bit decoding
I had a look at the assembly, thanks for the great effort!
I got lost in the decode_byte function. This is getting a bit too much time consuming for me, sorry.
If you can share the exe, I could try to run it through AIDA64 to see the graphs (no promise!! ;) )

Another idea if you cannot change the amount of channels being output: Could you saturate only every odder channels ? This could potentially help seeing a separation between samples ? Ideally, zeroing the other channel to see a clear cut. I don't know if this is doable for you ?

Sorry for not being more helpful. I hope it does help you push a bit further though.
Let us know how you get on!

Cheers,
Thomas

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

Hi Thomas,
Thomas wrote:
Fri Oct 29, 2021 5:20 pm
ch1 = [0000 1111][1111 1111] ---> [0000 0000][1111 1111]
1. ((uint16)l_pBytesRead[0]) = 0000 0000 0000 1111
2. ((uint16)l_pBytesRead[0]) << 4 = 0000 0000 1111 0000
3. (l_pBytesRead[1] & 0xF) = 0000 1111
4. (((uint16)l_pBytesRead[0]) << 4) | (l_pBytesRead[1] & 0xF) = 0000 0000 1111 1111
Thank you, will try this, and let you know.
Thomas wrote:
Fri Oct 29, 2021 5:20 pm
I got lost in the decode_byte function.
I can feel that it took me some time to find the relevant part and to mark them, but I don't have much experience with assembly language (I can say this is the first time I was working with it).

here are the exe files (folder and installation file)
https://filesender.garr.it/?s=download& ... 9928dc33ab
Thomas wrote:
Fri Oct 29, 2021 5:20 pm
Another idea if you cannot change the amount of channels being output: Could you saturate only every odder channels ? This could potentially help seeing a separation between samples ? Ideally, zeroing the other channel to see a clear cut. I don't know if this is doable for you ?
In theory, it should be doable. 1mV DC with reversed polarity on every other channel should do the thing, of course, if filters do not cut the dc. I might try to saturate only the first channel (Fp1), because I am not even sure if it really corresponds to the first 12bits of the 45byte chunk :|

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

Hi Thomas,

Here is the output of the channels when each single is connected to the ground and others are saturated:

https://github.com/miladinovic/KT88-160 ... 882400.txt

Edit:
Here are some new findings:

Code: Select all

Fp1 0b10100000 00001100 01111111 01111111 01111111 01111111 01010010 00001111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111101010101000100000110010100000000000001110010000101111011
Fp2 0b10100000 00001011 01111111 01111111 01111111 01111111 01010010 01110000 01111111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111101000010000100000001110000000000000001110000000101111010
F_3 0b10100000 00000111 01111110 01111111 01111111 01111111 01100111 01111111 01111111 01111111 00001111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100101010000100100101111000001101011101110000000101111100
F_4 0b10100000 00001111 01111101 01111111 01111111 01111111 01000111 01111111 01111111 01111111 01110000 01111111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100110111000100010100011001101111011101110111011101111011
C_3 0b10100000 00001111 01110011 01111111 01111111 01111111 01001111 01111111 01111111 01111111 01111111 01111111 01111111 00001111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100101010000100010101011001000001011101110111100001111101
C_4 0b10100000 00001111 01101111 01111111 01111111 01111111 01001111 01111111 01111111 01111111 01111111 01111111 01111111 01110000 01111111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100100111000100010100100000000110011101110111011001111100
P_3 0b10100000 00001101 00011100 00011010 01101111 00110100 01110010 01101101 00101100 01011110 01101101 01100010 01011010 01011101 01110110 01011111 00001110 00000000 00011111 01101101 00101011 01101101 01101110 00101000 00101101 01101110 01000011 00101010 01101101 00000110 00111010 01101110 01110110 00100111 01101110 00011011 00011011 01101110 0101110001101011000100000101100100000000000001110000001101110111
P_4 0b10100000 00001111 01111111 01111110 01111111 01111111 01011111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01110000 01111111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100001001000100000010011100110010000001110000001001111111
O_1 0b10100000 00001111 01111111 01111001 01111111 01111111 01101111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 00001111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100001001000100000011001101101100011101110111111001110111
O_2 0b10100000 00001111 01111111 01110111 01111111 01111111 01010110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01110000 01111111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100100010000000000101100000000000000001110000010001111010
F_7 0b10100000 00001111 01111111 01001111 01111111 01111111 01001111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 00001111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100110011000100010110100001001100011101110111011101111100
F_8 0b10100000 00001111 01111111 00111111 01111111 01111111 01001111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01110000 01111111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100100000000100010100111001000111011101110111101101111100
T_3 0b10100000 00001111 01111111 01111111 01111100 01111111 01001111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 00001111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100011110000100010100101000101100011101110111100001111011
T_4 0b10100000 00001111 01111111 01111111 01111011 01111111 01100111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01110000 01111111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100001110000100010010010100000100011101110000000101111010
T_5 0b10100000 00001111 01111111 01111111 01100111 01111111 01010111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 00001111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111100011101000100010100101001010001000001110000001001111010
T_6 0b10100000 00001111 01111111 01111111 01011111 01111111 01011011 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01110000 01111111 00000000 01111111 01111111 01111111 01111111 01111111 01111111 01111110 0111111101001110001000100010011001101100000001110010000101111011
F_z 0b10100000 00001111 01111111 01111111 00111111 01111110 01001111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 00001111 00000000 01111111 01111111 01111111 01111111 01111110 0111111100110001000100010101101101101010011101110111100001111011
P_z 0b10100000 00001111 01111111 01111111 01111111 01111101 01011111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01110000 01111111 00000000 01111111 01111111 01111111 01111110 0111111100110010000100010111011001010100000001110001001101111011
C_z 0b10100000 00001111 01111111 01111111 01111111 01110011 01000111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 00001111 00000000 01111111 01111110 0111111101000000000100100110001000000111011101110111011101111100
Pg1 0b10100000 00001011 00111111 01111111 01111111 01101111 01101111 01111111 01111111 01000110 01111110 01111111 01100001 01111111 00001100 00001111 01111111 00101100 01111111 01111111 01111111 00101000 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 11111110 01111111 01111111 01111111 01110000 01111111 00000000 01111110 0111111100100001000100010011110100001011011101110101110101111100
Pg2 0b10100000 00001111 01111111 01111111 01111111 00011111 01010111 01111111 01111111 01111111 01111111 01111111 01110011 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 00001110 0000000001011111000100100111001001010011000001110100011001111000
It seems that the data for EEG channels are encoded with 11bits starting from 8byte and further (see zeros pattern). However, there is also distinguishable patterns in the first couple of bytes (sure, we need to ignore P_3) that probably somehow adds an additional bit to the 11bits making it 12bit, as described by the system :? :?:

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

New upgrade,
in theory, this should be the correct protocol. I am not sure about the bit order yet, but that could be also solved.

x-used bits for the corresponding channel
0-unused bits
1-irrelevant bits for the corresponding channel
0b10100000 - stream init marker

Code: Select all

Fp1 0b10100000 000011xx 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
Fp2 0b10100000 00001x11 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
F_3 0b10100000 0000x111 0111111x 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
F_4 0b10100000 00001111 011111x1 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
C_3 0b10100000 00001111 0111xx11 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
C_4 0b10100000 00001111 011x1111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
P_3 0b10100000 00001111 0xx11111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
P_4 0b10100000 00001111 01111111 0111111x 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
O_1 0b10100000 00001111 01111111 01111xx1 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
O_2 0b10100000 00001111 01111111 0111x111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
F_7 0b10100000 00001111 01111111 01xx1111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
F_8 0b10100000 00001111 01111111 0x111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
T_3 0b10100000 00001111 01111111 01111111 011111xx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
T_4 0b10100000 00001111 01111111 01111111 01111x11 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
T_5 0b10100000 00001111 01111111 01111111 011xx111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
T_6 0b10100000 00001111 01111111 01111111 01x11111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
F_z 0b10100000 00001111 01111111 01111111 0x111111 0111111x 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
P_z 0b10100000 00001111 01111111 01111111 01111111 011111x1 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
C_z 0b10100000 00001111 01111111 01111111 01111111 0111xx11 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
Pg1 0b10100000 00001111 01111111 01111111 01111111 011x1111 01111111 01111111 01111111 01111111 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 11111110 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111110 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111
Pg2 0b10100000 00001111 01111111 01111111 01111111 0xx11111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111 01111111
CH1 0b10100000 00001111 01111111 01111111 01111111 01111111 0111111x 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111 01111111 01111111 01111111
CH2 0b10100000 00001111 01111111 01111111 01111111 01111111 011111xx 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111 01111111 01111111 01111111
CH3 0b10100000 00001111 01111111 01111111 01111111 01111111 01111x11 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx 01111111 01111111 01111111
CH4 0b10100000 00001111 01111111 01111111 01111111 01111111 01xx1111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0xxx1111 0xxxxxxx 01111111
CH5 0b10100000 00001111 01111111 01111111 01111111 01111111 0x111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 01111111 0111xxxx 01111111 0xxxxxxx

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

Finally, I manage to get it working in OpenVIbe. The bits location are 100% correct for each channel, and I assume also the bit order, but not completely sure...
https://www.youtube.com/watch?v=HBjhafKXALY

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

Upgrade.

Bit order.

I found the easiest way to figure out to generate an artificial signal of relatively low frequency (1 or 2hz) and to connect it to the input of the amplifier.

Image

I used the analogue one, as the digital new ones didn't have options to generate the signal <1Hz, which I needed to test the cut-off freq of the HP filter of the device.

So, I set it approx to 2Hz and if the bit order is somehow wrong, you would get something like this:

Image

After playing a little bit, I managed to figure it out.
Image

CH5 0b10100000 00001111 01111111 01111111 01111111 01111111 0x111111 ... 37bytes ... 0111yyyy 01111111 0zzzzzzz

CH5 u_int16=0000yyyyxzzzzzzz

Furthermore, the HW investigation of the double-stage amplification and HP filter with a cut-off at 0.034Hz a
https://patents.google.com/patent/CN103505200A/en

A further indication is the use of microcontroller MSP430F247 (also cited in the patent documentation)
Image Image
Image

EDIT.

Channels with 2 bits have this protocol.


Odd ch
CH4 0b10100000 00001111 01111111 01111111 01111111 01111111 01xw1111 ... 37bytes ... 0yyy1111 0zzzzzzz 01111111


CH4 u_int16=0000wyyyxzzzzzzz

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

For now, I made an LSL python script for streaming in a local network, next stage is an implementation in C++ for OV.
https://github.com/miladinovic/Contec_K ... reaming.py

If anyone have a better idea for bit shifting in the 2nd part, let me know :lol:

a1eks
Posts: 19
Joined: Sun Jan 21, 2018 6:26 pm

Re: Contec KT88-2400 - 24 channel EEG

Post by a1eks »

I made also the script for KT88-1600 (18 channels)
https://github.com/miladinovic/Contec_E ... reaming.py

Now, I need to implement the drivers.

Bedolaga
Posts: 2
Joined: Sun Nov 06, 2022 6:04 am

Re: Contec KT88-2400 - 24 channel EEG

Post by Bedolaga »

Dear a1eks,

Thanks for your great affords and findings!

I used your python script to get data from contec kt88-2400 and found some quite strange results.

First of all, your decoding algorithm for 12 bits data to 2 bytes seems to be correct and fully equivalent to native code in manufacturer’s eeg24.exe software.

After disassembling this executable we can see codes like that:

Code: Select all

.text:00568035                 cmp     [ebp+arg_4], 2Eh
.text:00568039                 jnz     short loc_56804D
.text:0056803B                 mov     eax, [ebp+arg_0]
.text:0056803E                 push    eax
.text:0056803F                 lea     ecx, [ebp+Dst]
.text:00568042                 push    ecx
.text:00568043                 call    sub_4517D8      ; Decoding current chunk
.text:00568048                 add     esp, 8
.text:0056804B                 jmp     short loc_568063
.text:0056804D ; ----------------------------------------------------------
.text:0056804D
.text:0056804D loc_56804D:                             ; CODE XREF: sub_567EA9+190j


.text:004517D8 sub_4517D8      proc near               ; CODE XREF: sub_567EA9+19Ap
.text:004517D8
.text:004517D8 arg_0           = dword ptr  8
.text:004517D8 arg_4           = dword ptr  0Ch
.text:004517D8
.text:004517D8                 push    ebp 
.text:004517D9                 mov     ebp, esp
.text:004517DB                 mov     eax, [ebp+arg_0]
.text:004517DE                 xor     ecx, ecx
.text:004517E0                 mov     cl, [eax+7]
.text:004517E3                 mov     edx, [ebp+arg_0]
.text:004517E6                 xor     eax, eax
.text:004517E8                 mov     al, [edx+1]
.text:004517EB                 shl     eax, 7
.text:004517EE                 and     eax, 80h
.text:004517F3                 or      ecx, eax
.text:004517F5                 sar     ecx, 4
.text:004517F8                 shl     ecx, 4
.text:004517FB                 and     ecx, 0FFh
.text:00451801                 mov     edx, [ebp+arg_4]
.text:00451804                 mov     [edx], cl
.text:00451806                 mov     eax, [ebp+arg_0]
.text:00451809                 xor     ecx, ecx
.text:0045180B                 mov     cl, [eax+8]
.text:0045180E                 mov     edx, [ebp+arg_0]
.text:00451811                 xor     eax, eax
.text:00451813                 mov     al, [edx+1]
.text:00451816                 shl     eax, 6
.text:00451819                 and     eax, 80h
.text:0045181E                 or      ecx, eax
.text:00451820                 mov     edx, [ebp+arg_4]
.text:00451823                 mov     [edx+1], cl
.text:00451826                 mov     eax, [ebp+arg_0]
.text:00451829                 xor     ecx, ecx
.text:0045182B                 mov     cl, [eax+7]
.text:0045182E                 shl     ecx, 4
.text:00451831                 and     ecx, 0FFh
.text:00451837                 mov     edx, [ebp+arg_4]
.text:0045183A                 mov     [edx+2], cl
.text:0045183D                 mov     eax, [ebp+arg_0]
.text:00451840                 xor     ecx, ecx
.text:00451842                 mov     cl, [eax+9]
.text:00451845                 mov     edx, [ebp+arg_0]
.text:00451848                 xor     eax, eax
.text:0045184A                 mov     al, [edx+1]
.text:0045184D                 shl     eax, 5
.text:00451850                 and     eax, 80h
.text:00451855                 or      ecx, eax
.text:00451857                 mov     edx, [ebp+arg_4]
.text:0045185A                 mov     [edx+3], cl
…

Moreover, if we link together A1, A and Fp1 sockets on eeg board we’ll get almost flat line, near zero uV on channel[0]:

Please see below values in Channel[0] in binary, channel[0] - 2048 in decimals:

Code: Select all

0000011111111111 -1
0000100000000001 1
0000100000000101 5
0000100000000011 3
0000011111111110 -2
0000011111111111 -1
0000100000000100 4
0000100000000010 2
0000011111111110 -2
0000100000000010 2
0000100000000100 4
0000100000000100 4
0000100000000001 1
0000100000000010 2
0000100000000011 3
0000100000000010 2
0000011111111101 -3
0000011111111111 -1
0000100000000100 4
0000100000000001 1
0000100000000000 0
0000100000000100 4
0000100000001001 9
0000100000000100 4
0000100000000001 1
0000100000000100 4
0000100000001000 8
0000100000000101 5
0000011111111101 -3
0000011111111111 -1
0000100000000100 4
But if we inject sinusoidal, 3Hz, 50uV signal to Fp1-A1, we’ll get strange, physically irrelevant data in channel[0]:

Code: Select all

0000110000001111 1039
0000101001001110 590
0000010011101111 -785
0000011011011100 -292
0000110001011000 1112
0000101010001000 648
0000010100100110 -730
0000011011111111 -257
0000110010001010 1162
0000101010110101 693
0000010101010101 -683
0000011100100111 -217
0000110010110100 1204
0000101011001100 716
0000010101111010 -646
0000011101001100 -180
0000110011001000 1224
0000101011011010 730
0000010101111100 -644
0000011101010010 -174
0000110011001011 1227
0000101011011001 729
0000010101110000 -656
0000011100111101 -195
0000110010101100 1196
0000101010111011 699
0000010101010000 -688
0000011100010001 -239
0000110010000001 1153
0000101010011000 664
0000010100010011 -749
0000011011100001 -287
0000110001001001 1097
0000101001011010 602
0000010011001111 -817
0000011010011110 -354
0000110000000001 1025
0000101000100110 550
0000010010010111 -873
0000011001100101 -411
0000101110111001 953
0000100111010101 469
0000010001001101 -947
0000011000101100 -468
0000101110001001 905
0000100110111100 444
0000010000101011 -981
0000011000010100 -492
0000101101101111 879
0000100110101110 430
0000010000010110 -1002
0000011000000110 -506
0000101101011110 862
0000100110100100 420
0000010000011011 -997
0000011000000110 -506
0000101101101000 872
0000100110111011 443
0000010000111010 -966
0000011000100110 -474
0000101110010001 913
0000100111001111 463
0000010001010001 -943
0000011001001000 -440
0000101111000100 964
0000101000010010 530
0000010010011111 -865
0000011001111101 -387
0000110000000011 1027
0000101000110011 563
0000010011011000 -808
0000011010111010 -326
0000110001000110 1094
0000101001101000 616
0000010100010101 -747
0000011011100100 -284
0000110001101110 1134
0000101010001111 655
0000010100111010 -710
0000011100010000 -240
0000110010001001 1161
0000101010101100 684
0000010101011010 -678
0000011100011111 -225
0000110010011110 1182
0000101010100111 679
0000010101011110 -674
0000011100011110 -226
0000110010011011 1179
0000101010100001 673
0000010101001010 -694
0000011100000100 -252
0000110001110111 1143
0000101010001000 648
0000010100011101 -739
0000011011010111 -297
0000110001000000 1088
0000101001010000 592
0000010011011110 -802
0000011010100101 -347
0000110000001000 1032
0000101000010101 533
0000010010011000 -872
0000011001101000 -408
0000101111000100 964
0000100111101001 489
Though, this signal is decoded in native software correctly:

Image

So, there is still some mess with correct decoding of stream from amplifier, I believe.

Did you test your algorithm with injection of sinusoidal signal?

Thanks again,

Alex

Post Reply