Commit bd445dce authored by Max-Ferdinand Suffel's avatar Max-Ferdinand Suffel
Browse files

Add node.js server for ThinkGear communication

parent 4baf8920
// DEBUG switch.
var DEBUG = false;
// Import filesystem.
var fs = require('fs');
var recordFile = 'record.json';
// Import socket.io.
var io = require('socket.io')();
// Import enums.
var Enum = require('enum');
// Create serial port object.
var SerialPort = require("serialport").SerialPort;
// ThinkGear Communication Protocol constants.
var AUTOCONNECT = 0xC2;
var DISCONNECT = 0xC1;
var SYNC = 0xAA;
var EXCODE = 0x55;
var CODE_ATTENTION = 0x04
var CODE_MEDITATION = 0x05
// Obtain name of user-speficied serial port device.
var serialPortName = process.argv[2];
if (!serialPortName) {
console.log("Serial port name missing! => node NodeTGC.js <path-to-serial-port-device>")
process.exit(1);
}
// Open serial port of the Neurosky MindWave using the given name.
var nmwSerialPort = new SerialPort(serialPortName, {
baudRate : 115200 // According the chip specification the baudrate has to be 115200.
});
var nmwSerialPortOpen = false;
// Start socket.io server listening on port 3000.
io.listen(3000);
// Handle user input.
var readline = require('readline'),
rl = readline.createInterface(process.stdin, process.stdout);
rl.on('close', function() {
if (nmwSerialPortOpen) {
// Disconnect from the Neurosky MindWave.
nmwSerialPort.write(new Buffer([DISCONNECT], 'hex'), function(error, results) {
if (error || results != 1) {
console.log("Could not disconnect from the Neurosky MindWave." + err);
process.exit(1);
} else {
console.log("Disconnect...");
}
});
nmwSerialPort.drain();
nmwSerialPort.close();
}
io.close();
process.exit(0);
});
// Listen for open-events: Called when the serial port connection is established.
nmwSerialPort.on("open", function (error) {
if (error) {
console.log("Serial port failed to open: " + error);
} else {
nmwSerialPortOpen = true;
console.log("Serial port to \"" + serialPortName + "\" successfully openend.");
// Attach a listener for the incoming packets from the serial port.
nmwSerialPort.on("data", receivePackets);
// Autoconnect to the first discovered Neurosky MindWave.
nmwSerialPort.write(new Buffer([AUTOCONNECT], 'hex'), function(error, results) {
if (error || results != 1) {
console.log("Could not autoconnect to the Neurosky MindWave." + err);
process.exit(1);
} else {
console.log("Connecting...");
}
});
}
});
// State Machine for the serial port data processing.
var ProcessState = new Enum(['SYNC', 'SYNC_CHECK', 'PAYLOAD_LENGTH', 'PAYLOAD', 'CHECKSUM']);
var state = ProcessState.SYNC; // Initial state is SYNC.
var payloadLength;
var payloadIndex;
var payloadBuffer;
var checksum;
// Process the incoming packets from the serial port.
// TODO: Decouple processing of the packets from the checksum check => new PROCESS state!
function receivePackets (data) {
for (var i = 0; i < data.length; i++) {
// Obtain the next byte from the serial port connection.
var next_byte = data.readUInt8(i);
if (DEBUG) console.log ("Process Next Byte: " + next_byte.toString(16));
switch (state) {
// Seek for synchronization byte.
case ProcessState.SYNC:
if (next_byte == SYNC) {
state = ProcessState.SYNC_CHECK;
if (DEBUG) console.log("SYNC");
}
break;
// Double check the synchronization byte.
case ProcessState.SYNC_CHECK:
if (next_byte == SYNC) {
state = ProcessState.PAYLOAD_LENGTH;
if (DEBUG) console.log("SYNC DOUBLECHECK");
} else {
state = ProcessState.SYNC;
if (DEBUG) console.log("SYNC DOUBLECHECK FAILED");
}
break;
// Obtain the payload length.
case ProcessState.PAYLOAD_LENGTH:
payloadLength = next_byte;
if (payloadLength > 170) {
state = ProcessState.SYNC;
if (DEBUG) console.log("PAYLOAD TOO LARGE (>170)");
} else if (payloadLength == 170) {
if (DEBUG) console.log("PAYLOAD == SYNC");
} else { // Pre-Condition: payLoadLength < 170
// Create a suitable payload buffer for the upcoming serial data.
payloadBuffer = new Buffer(payloadLength, 'hex');
payloadIndex = 0;
checksum = 0;
state = ProcessState.PAYLOAD;
if (DEBUG) console.log("PAYLOAD LENGTH IS: " + payloadLength);
}
break;
// Receive the payload and compute the checksum in parallel.
case ProcessState.PAYLOAD:
if (payloadIndex < payloadLength) {
payloadBuffer[payloadIndex] = next_byte;
checksum += next_byte;
++payloadIndex;
if (DEBUG) console.log("RECEIVE PAYLOAD [" + payloadIndex + "," + payloadBuffer.toString('hex') + "]");
}
if (payloadIndex == payloadLength) {
checksum = checksum & 0xFF;
checksum = (~checksum) & 0xFF;
state = ProcessState.CHECKSUM;
if (DEBUG) console.log("PREPARE CHECKSUM CHECK");
}
break;
// Validate the payload with the following checksum byte;
case ProcessState.CHECKSUM:
if (checksum == next_byte) {
if (DEBUG) console.log("CHECKSUM VALID");
// Checksum was valid, thus, processing packet given by the payload since is okay.
processPacket();
} else { // Pre-Condition: checksum != data
if (DEBUG) console.log("CHECKSUM IS WRONG [" + checksum + "!=" + next_byte + "]");
}
state = ProcessState.SYNC;
break;
default:
if (DEBUG) console.log("CRITICAL ERROR");
process.exit(1);
}
}
}
function processPacket() {
if (DEBUG) console.log("Process Packet");
// Create an empty EEG package.
var EEGPackage = {};
// Process all data rows in the received serial data package.
var payloadPointer = 0;
while (payloadPointer < payloadBuffer.length) {
// Determine the Extended Code Level (EXCODE) and CODE.
var extendedCodeLevel = 0;
var code;
while (payloadPointer < payloadBuffer.length) {
var next_byte = payloadBuffer.readUInt8(payloadPointer);
++payloadPointer;
if (next_byte == EXCODE) {
++extendedCodeLevel;
} else {
// The first byte unequal the EXCODE byte specifies the CODE.
code = next_byte;
break;
}
}
if (DEBUG) console.log ("extendedCodeLevel: " + extendedCodeLevel.toString(16) + ", code: " + code.toString(16));
if (extendedCodeLevel == 0) {
if (code <= 0x7F) { // Single-Byte CODEs
if (DEBUG) console.log ("Single-Byte CODE");
if (payloadPointer < payloadBuffer.length) {
var value = payloadBuffer.readUInt8(payloadPointer);
++payloadPointer;
switch (code) {
case CODE_ATTENTION:
EEGPackage["attention"] = value;
console.log ("ATTENTION: " + value);
break;
case CODE_MEDITATION:
EEGPackage["meditation"] = value;
console.log ("MEDITATION: " + value);
break;
default:
if (DEBUG) console.log ("DEFAULT: " + value);
}
} else {
if (DEBUG) console.log("CRITICAL ERROR");
process.exit(1);
}
} else { // Multi-Byte CODEs, Pre-Condition: code >= 0x80
if (DEBUG) console.log ("Multi-Byte CODE");
if (payloadPointer < payloadBuffer.length) {
var vlength = payloadBuffer.readUInt8(payloadPointer);
++payloadPointer;
if (DEBUG) console.log("payloadPointer: " + payloadPointer + ", vlength: " + vlength);
// Skip multi-byte data asap.
payloadPointer += vlength;
if (DEBUG) console.log("next payloadPointer: " + payloadPointer);
} else {
if (DEBUG) console.log("CRITICAL ERROR");
process.exit(1);
}
}
} else {
console.log("Not implemented!");
process.exit(1);
}
}
// Record/send the EEGPackage.
if (Object.getOwnPropertyNames(EEGPackage).length !== 0) {
var EEGPackage_JSON = JSON.stringify(EEGPackage);
// Append the EEGPackage to the record file.
fs.appendFile(recordFile, EEGPackage_JSON + "\n", function (err) {
if (err) throw err;
});
// Send to clients listening via sockets.
io.emit('get-EEG', JSON.stringify(EEGPackage));
if (DEBUG) console.log(EEGPackage_JSON);
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment