-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #130 from aj-ptw/add-exp-python
Add exp python
- Loading branch information
Showing
6 changed files
with
415 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import json | ||
import sys | ||
import numpy as np | ||
import time | ||
import zmq | ||
|
||
|
||
class Interface: | ||
def __init__(self, verbose=False): | ||
context = zmq.Context() | ||
self._socket = context.socket(zmq.PAIR) | ||
self._socket.connect("tcp://localhost:3004") | ||
|
||
self.verbose = verbose | ||
|
||
if self.verbose: | ||
print "Client Ready!" | ||
|
||
# Send a quick message to tell node process we are up and running | ||
self.send(json.dumps({ | ||
'action': 'started', | ||
'command': 'status', | ||
'message': time.time()*1000.0 | ||
})) | ||
|
||
def send(self, msg): | ||
""" | ||
Sends a message to TCP server | ||
:param msg: str | ||
A string to send to node TCP server, could be a JSON dumps... | ||
:return: None | ||
""" | ||
if self.verbose: | ||
print '<- out ' + msg | ||
self._socket.send(msg) | ||
return | ||
|
||
def recv(self): | ||
""" | ||
Checks the ZeroMQ for data | ||
:return: str | ||
String of data | ||
""" | ||
return self._socket.recv() | ||
|
||
|
||
class RingBuffer(np.ndarray): | ||
"""A multidimensional ring buffer.""" | ||
|
||
def __new__(cls, input_array): | ||
obj = np.asarray(input_array).view(cls) | ||
return obj | ||
|
||
def __array_finalize__(self, obj): | ||
if obj is None: | ||
return | ||
|
||
def __array_wrap__(self, out_arr, context=None): | ||
return np.ndarray.__array_wrap__(self, out_arr, context) | ||
|
||
def append(self, x): | ||
"""Adds element x to the ring buffer.""" | ||
x = np.asarray(x) | ||
self[:, :-1] = self[:, 1:] | ||
self[:, -1] = x | ||
|
||
|
||
def main(argv): | ||
nb_chan = 8 | ||
verbose = True | ||
|
||
# Create a new python interface. | ||
interface = Interface(verbose=verbose) | ||
# Signal buffer | ||
signal = RingBuffer(np.zeros((nb_chan + 1, 2500))) | ||
|
||
while True: | ||
msg = interface.recv() | ||
try: | ||
dicty = json.loads(msg) | ||
action = dicty.get('action') | ||
command = dicty.get('command') | ||
message = dicty.get('message') | ||
|
||
if command == 'sample': | ||
if action == 'process': | ||
# Do sample processing here | ||
try: | ||
if type(message) is not dict: | ||
print "sample is not a dict", message | ||
raise ValueError | ||
# Get keys of sample | ||
data = np.zeros(9) | ||
|
||
data[:-1] = message.get('channelData') | ||
data[-1] = message.get('timeStamp') | ||
|
||
# Add data to end of ring buffer | ||
signal.append(data) | ||
|
||
print message.get('sampleNumber') | ||
except ValueError as e: | ||
print e | ||
elif command == 'status': | ||
if action == 'active': | ||
interface.send(json.dumps({ | ||
'action': 'alive', | ||
'command': 'status', | ||
'message': time.time() * 1000.0 | ||
})) | ||
|
||
except BaseException as e: | ||
print e | ||
|
||
|
||
if __name__ == '__main__': | ||
main(sys.argv[1:]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
/** | ||
* This is an example from the readme.md | ||
* On windows you should run with PowerShell not git bash. | ||
* Install | ||
* [nodejs](https://nodejs.org/en/) | ||
* | ||
* To run: | ||
* change directory to this file `cd examples/debug` | ||
* do `npm install` | ||
* then `npm start` | ||
*/ | ||
var OpenBCIBoard = require('openbci').OpenBCIBoard; | ||
var port_pub = 'tcp://127.0.0.1:3004'; | ||
var zmq = require('zmq-prebuilt'); | ||
var socket = zmq.socket('pair'); | ||
var simulate = true; // Sends synthetic data | ||
var debug = false; // Pretty print any bytes in and out... it's amazing... | ||
var verbose = true; // Adds verbosity to functions | ||
|
||
var ourBoard = new OpenBCIBoard({ | ||
simulate: simulate, | ||
simulatorFirmwareVersion: 'v2', | ||
debug: debug, | ||
verbose: verbose | ||
}); | ||
|
||
var sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event! | ||
var timeSyncPossible = false; | ||
var resyncPeriodMin = 1; | ||
var secondsInMinute = 60; | ||
var resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute; | ||
|
||
ourBoard.autoFindOpenBCIBoard().then(portName => { | ||
if (portName) { | ||
/** | ||
* Connect to the board with portName | ||
* i.e. ourBoard.connect(portName)..... | ||
*/ | ||
// Call to connect | ||
ourBoard.connect(portName) | ||
.then(() => { | ||
ourBoard.on('ready', () => { | ||
|
||
// Get the sample rate after 'ready' | ||
sampleRate = ourBoard.sampleRate(); | ||
// Find out if you can even time sync, you must be using v2 and this is only accurate after a `.softReset()` call which is called internally on `.connect()`. We parse the `.softReset()` response for the presence of firmware version 2 properties. | ||
timeSyncPossible = ourBoard.usingVersionTwoFirmware(); | ||
|
||
if (timeSyncPossible) { | ||
ourBoard.streamStart() | ||
.catch(err => { | ||
console.log(`stream start: ${err}`); | ||
}); | ||
} else { | ||
console.log('not able to time sync'); | ||
} | ||
}) | ||
}) | ||
.catch(err => { | ||
console.log(`connect: ${err}`); | ||
}); | ||
} else { | ||
/** Unable to auto find OpenBCI board */ | ||
console.log('Unable to auto find OpenBCI board'); | ||
} | ||
}); | ||
|
||
var sampleFunc = sample => { | ||
if (sample._count % resyncPeriod === 0) { | ||
ourBoard.syncClocksFull() | ||
.then(syncObj => { | ||
// Sync was successful | ||
if (syncObj.valid) { | ||
// Log the object to check it out! | ||
console.log(`timeOffset`, syncObj.timeOffsetMaster); | ||
} else { | ||
// Retry it | ||
console.log(`Was not able to sync... retry!`); | ||
} | ||
}); | ||
} | ||
|
||
if (sample.timeStamp) { // true after the first successful sync | ||
if (sample.timeStamp < 10 * 60 * 60 * 1000) { // Less than 10 hours | ||
console.log(`Bad time sync ${sample.timeStamp}`); | ||
} else { | ||
sendToPython({ | ||
action: 'process', | ||
command: 'sample', | ||
message: sample | ||
}); | ||
} | ||
} | ||
}; | ||
|
||
// Subscribe to your functions | ||
ourBoard.on('sample', sampleFunc); | ||
|
||
// ZMQ fun | ||
|
||
socket.bind(port_pub, function (err) { | ||
if (err) throw err; | ||
console.log(`bound to ${port_pub}`); | ||
}); | ||
|
||
/** | ||
* Used to send a message to the Python process. | ||
* @param {Object} interProcessObject The standard inter-process object. | ||
* @return {None} | ||
*/ | ||
var sendToPython = (interProcessObject, verbose) => { | ||
if (verbose) { | ||
console.log(`<- out ${JSON.stringify(interProcessObject)}`); | ||
} | ||
if (socket) { | ||
socket.send(JSON.stringify(interProcessObject)); | ||
} | ||
}; | ||
|
||
var receiveFromPython = (raw_data) => { | ||
try { | ||
let body = JSON.parse(raw_data); // five because `resp ` | ||
processInterfaceObject(body); | ||
} catch (err) { | ||
console.log('in -> ' + 'bad json'); | ||
} | ||
}; | ||
|
||
socket.on('message', receiveFromPython); | ||
|
||
var sendStatus = () => { | ||
sendToPython({'action': 'active', 'message': 'ready', 'command': 'status'}, true); | ||
}; | ||
|
||
sendStatus(); | ||
|
||
/** | ||
* Process an incoming message | ||
* @param {String} body A stringify JSON object that shall be parsed. | ||
* @return {None} | ||
*/ | ||
var processInterfaceObject = (body) => { | ||
switch (body.command) { | ||
case 'status': | ||
processStatus(body); | ||
break; | ||
default: | ||
unrecognizedCommand(body); | ||
break; | ||
} | ||
}; | ||
|
||
/** | ||
* Used to process a status related command from TCP IPC. | ||
* @param {Object} body | ||
* @return {None} | ||
*/ | ||
var processStatus = (body) => { | ||
switch (body.action) { | ||
case 'started': | ||
console.log(`python started @ ${body.message}ms`); | ||
break; | ||
case 'alive': | ||
console.log(`python duplex communication test completed @ ${body.message}ms`); | ||
break; | ||
default: | ||
unrecognizedCommand(body); | ||
break; | ||
} | ||
}; | ||
|
||
function unrecognizedCommand (body) { | ||
console.log(`unrecognizedCommand ${body}`); | ||
} | ||
|
||
function exitHandler (options, err) { | ||
if (options.cleanup) { | ||
if (verbose) console.log('clean'); | ||
/** Do additional clean up here */ | ||
} | ||
if (err) console.log(err.stack); | ||
if (options.exit) { | ||
if (verbose) console.log('exit'); | ||
ourBoard.disconnect().catch(console.log); | ||
} | ||
} | ||
|
||
if (process.platform === "win32") { | ||
const rl = require("readline").createInterface({ | ||
input: process.stdin, | ||
output: process.stdout | ||
}); | ||
|
||
rl.on("SIGINT", function () { | ||
process.emit("SIGINT"); | ||
}); | ||
} | ||
|
||
// do something when app is closing | ||
process.on('exit', exitHandler.bind(null, { | ||
cleanup: true | ||
})); | ||
|
||
// catches ctrl+c event | ||
process.on('SIGINT', exitHandler.bind(null, { | ||
exit: true | ||
})); | ||
|
||
// catches uncaught exceptions | ||
process.on('uncaughtException', exitHandler.bind(null, { | ||
exit: true | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "python", | ||
"version": "1.0.0", | ||
"description": "node to python example", | ||
"main": "index.js", | ||
"scripts": { | ||
"start": "concurrently --kill-others \"python handoff.py\" \"node index.js\"", | ||
"start-node": "node index.js", | ||
"start-verbose": "concurrently --kill-others \"python handoff.py -v\" \"node index.js\"", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"keywords": [ | ||
"python", | ||
"openbci", | ||
"node" | ||
], | ||
"author": "AJ Keller", | ||
"license": "MIT", | ||
"dependencies": { | ||
"openbci": "^1.4.2", | ||
"zmq-prebuilt": "^2.1.0" | ||
}, | ||
"devEngines": { | ||
"node": "<=6.x", | ||
"npm": ">=3.x" | ||
}, | ||
"devDependencies": { | ||
"concurrently": "^3.1.0" | ||
} | ||
} |
Oops, something went wrong.