Skip to content

Commit f96cdd9

Browse files
author
AJ Keller
authored
Merge pull request #130 from aj-ptw/add-exp-python
Add exp python
2 parents c2c75fe + 4ce630d commit f96cdd9

File tree

6 files changed

+415
-1
lines changed

6 files changed

+415
-1
lines changed

examples/python/handoff.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import json
2+
import sys
3+
import numpy as np
4+
import time
5+
import zmq
6+
7+
8+
class Interface:
9+
def __init__(self, verbose=False):
10+
context = zmq.Context()
11+
self._socket = context.socket(zmq.PAIR)
12+
self._socket.connect("tcp://localhost:3004")
13+
14+
self.verbose = verbose
15+
16+
if self.verbose:
17+
print "Client Ready!"
18+
19+
# Send a quick message to tell node process we are up and running
20+
self.send(json.dumps({
21+
'action': 'started',
22+
'command': 'status',
23+
'message': time.time()*1000.0
24+
}))
25+
26+
def send(self, msg):
27+
"""
28+
Sends a message to TCP server
29+
:param msg: str
30+
A string to send to node TCP server, could be a JSON dumps...
31+
:return: None
32+
"""
33+
if self.verbose:
34+
print '<- out ' + msg
35+
self._socket.send(msg)
36+
return
37+
38+
def recv(self):
39+
"""
40+
Checks the ZeroMQ for data
41+
:return: str
42+
String of data
43+
"""
44+
return self._socket.recv()
45+
46+
47+
class RingBuffer(np.ndarray):
48+
"""A multidimensional ring buffer."""
49+
50+
def __new__(cls, input_array):
51+
obj = np.asarray(input_array).view(cls)
52+
return obj
53+
54+
def __array_finalize__(self, obj):
55+
if obj is None:
56+
return
57+
58+
def __array_wrap__(self, out_arr, context=None):
59+
return np.ndarray.__array_wrap__(self, out_arr, context)
60+
61+
def append(self, x):
62+
"""Adds element x to the ring buffer."""
63+
x = np.asarray(x)
64+
self[:, :-1] = self[:, 1:]
65+
self[:, -1] = x
66+
67+
68+
def main(argv):
69+
nb_chan = 8
70+
verbose = True
71+
72+
# Create a new python interface.
73+
interface = Interface(verbose=verbose)
74+
# Signal buffer
75+
signal = RingBuffer(np.zeros((nb_chan + 1, 2500)))
76+
77+
while True:
78+
msg = interface.recv()
79+
try:
80+
dicty = json.loads(msg)
81+
action = dicty.get('action')
82+
command = dicty.get('command')
83+
message = dicty.get('message')
84+
85+
if command == 'sample':
86+
if action == 'process':
87+
# Do sample processing here
88+
try:
89+
if type(message) is not dict:
90+
print "sample is not a dict", message
91+
raise ValueError
92+
# Get keys of sample
93+
data = np.zeros(9)
94+
95+
data[:-1] = message.get('channelData')
96+
data[-1] = message.get('timeStamp')
97+
98+
# Add data to end of ring buffer
99+
signal.append(data)
100+
101+
print message.get('sampleNumber')
102+
except ValueError as e:
103+
print e
104+
elif command == 'status':
105+
if action == 'active':
106+
interface.send(json.dumps({
107+
'action': 'alive',
108+
'command': 'status',
109+
'message': time.time() * 1000.0
110+
}))
111+
112+
except BaseException as e:
113+
print e
114+
115+
116+
if __name__ == '__main__':
117+
main(sys.argv[1:])

examples/python/index.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/**
2+
* This is an example from the readme.md
3+
* On windows you should run with PowerShell not git bash.
4+
* Install
5+
* [nodejs](https://nodejs.org/en/)
6+
*
7+
* To run:
8+
* change directory to this file `cd examples/debug`
9+
* do `npm install`
10+
* then `npm start`
11+
*/
12+
var OpenBCIBoard = require('openbci').OpenBCIBoard;
13+
var port_pub = 'tcp://127.0.0.1:3004';
14+
var zmq = require('zmq-prebuilt');
15+
var socket = zmq.socket('pair');
16+
var simulate = true; // Sends synthetic data
17+
var debug = false; // Pretty print any bytes in and out... it's amazing...
18+
var verbose = true; // Adds verbosity to functions
19+
20+
var ourBoard = new OpenBCIBoard({
21+
simulate: simulate,
22+
simulatorFirmwareVersion: 'v2',
23+
debug: debug,
24+
verbose: verbose
25+
});
26+
27+
var sampleRate = 250; // Default to 250, ALWAYS verify with a call to `.sampleRate()` after 'ready' event!
28+
var timeSyncPossible = false;
29+
var resyncPeriodMin = 1;
30+
var secondsInMinute = 60;
31+
var resyncPeriod = ourBoard.sampleRate() * resyncPeriodMin * secondsInMinute;
32+
33+
ourBoard.autoFindOpenBCIBoard().then(portName => {
34+
if (portName) {
35+
/**
36+
* Connect to the board with portName
37+
* i.e. ourBoard.connect(portName).....
38+
*/
39+
// Call to connect
40+
ourBoard.connect(portName)
41+
.then(() => {
42+
ourBoard.on('ready', () => {
43+
44+
// Get the sample rate after 'ready'
45+
sampleRate = ourBoard.sampleRate();
46+
// 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.
47+
timeSyncPossible = ourBoard.usingVersionTwoFirmware();
48+
49+
if (timeSyncPossible) {
50+
ourBoard.streamStart()
51+
.catch(err => {
52+
console.log(`stream start: ${err}`);
53+
});
54+
} else {
55+
console.log('not able to time sync');
56+
}
57+
})
58+
})
59+
.catch(err => {
60+
console.log(`connect: ${err}`);
61+
});
62+
} else {
63+
/** Unable to auto find OpenBCI board */
64+
console.log('Unable to auto find OpenBCI board');
65+
}
66+
});
67+
68+
var sampleFunc = sample => {
69+
if (sample._count % resyncPeriod === 0) {
70+
ourBoard.syncClocksFull()
71+
.then(syncObj => {
72+
// Sync was successful
73+
if (syncObj.valid) {
74+
// Log the object to check it out!
75+
console.log(`timeOffset`, syncObj.timeOffsetMaster);
76+
} else {
77+
// Retry it
78+
console.log(`Was not able to sync... retry!`);
79+
}
80+
});
81+
}
82+
83+
if (sample.timeStamp) { // true after the first successful sync
84+
if (sample.timeStamp < 10 * 60 * 60 * 1000) { // Less than 10 hours
85+
console.log(`Bad time sync ${sample.timeStamp}`);
86+
} else {
87+
sendToPython({
88+
action: 'process',
89+
command: 'sample',
90+
message: sample
91+
});
92+
}
93+
}
94+
};
95+
96+
// Subscribe to your functions
97+
ourBoard.on('sample', sampleFunc);
98+
99+
// ZMQ fun
100+
101+
socket.bind(port_pub, function (err) {
102+
if (err) throw err;
103+
console.log(`bound to ${port_pub}`);
104+
});
105+
106+
/**
107+
* Used to send a message to the Python process.
108+
* @param {Object} interProcessObject The standard inter-process object.
109+
* @return {None}
110+
*/
111+
var sendToPython = (interProcessObject, verbose) => {
112+
if (verbose) {
113+
console.log(`<- out ${JSON.stringify(interProcessObject)}`);
114+
}
115+
if (socket) {
116+
socket.send(JSON.stringify(interProcessObject));
117+
}
118+
};
119+
120+
var receiveFromPython = (raw_data) => {
121+
try {
122+
let body = JSON.parse(raw_data); // five because `resp `
123+
processInterfaceObject(body);
124+
} catch (err) {
125+
console.log('in -> ' + 'bad json');
126+
}
127+
};
128+
129+
socket.on('message', receiveFromPython);
130+
131+
var sendStatus = () => {
132+
sendToPython({'action': 'active', 'message': 'ready', 'command': 'status'}, true);
133+
};
134+
135+
sendStatus();
136+
137+
/**
138+
* Process an incoming message
139+
* @param {String} body A stringify JSON object that shall be parsed.
140+
* @return {None}
141+
*/
142+
var processInterfaceObject = (body) => {
143+
switch (body.command) {
144+
case 'status':
145+
processStatus(body);
146+
break;
147+
default:
148+
unrecognizedCommand(body);
149+
break;
150+
}
151+
};
152+
153+
/**
154+
* Used to process a status related command from TCP IPC.
155+
* @param {Object} body
156+
* @return {None}
157+
*/
158+
var processStatus = (body) => {
159+
switch (body.action) {
160+
case 'started':
161+
console.log(`python started @ ${body.message}ms`);
162+
break;
163+
case 'alive':
164+
console.log(`python duplex communication test completed @ ${body.message}ms`);
165+
break;
166+
default:
167+
unrecognizedCommand(body);
168+
break;
169+
}
170+
};
171+
172+
function unrecognizedCommand (body) {
173+
console.log(`unrecognizedCommand ${body}`);
174+
}
175+
176+
function exitHandler (options, err) {
177+
if (options.cleanup) {
178+
if (verbose) console.log('clean');
179+
/** Do additional clean up here */
180+
}
181+
if (err) console.log(err.stack);
182+
if (options.exit) {
183+
if (verbose) console.log('exit');
184+
ourBoard.disconnect().catch(console.log);
185+
}
186+
}
187+
188+
if (process.platform === "win32") {
189+
const rl = require("readline").createInterface({
190+
input: process.stdin,
191+
output: process.stdout
192+
});
193+
194+
rl.on("SIGINT", function () {
195+
process.emit("SIGINT");
196+
});
197+
}
198+
199+
// do something when app is closing
200+
process.on('exit', exitHandler.bind(null, {
201+
cleanup: true
202+
}));
203+
204+
// catches ctrl+c event
205+
process.on('SIGINT', exitHandler.bind(null, {
206+
exit: true
207+
}));
208+
209+
// catches uncaught exceptions
210+
process.on('uncaughtException', exitHandler.bind(null, {
211+
exit: true
212+
}));

examples/python/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "python",
3+
"version": "1.0.0",
4+
"description": "node to python example",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "concurrently --kill-others \"python handoff.py\" \"node index.js\"",
8+
"start-node": "node index.js",
9+
"start-verbose": "concurrently --kill-others \"python handoff.py -v\" \"node index.js\"",
10+
"test": "echo \"Error: no test specified\" && exit 1"
11+
},
12+
"keywords": [
13+
"python",
14+
"openbci",
15+
"node"
16+
],
17+
"author": "AJ Keller",
18+
"license": "MIT",
19+
"dependencies": {
20+
"openbci": "^1.4.2",
21+
"zmq-prebuilt": "^2.1.0"
22+
},
23+
"devEngines": {
24+
"node": "<=6.x",
25+
"npm": ">=3.x"
26+
},
27+
"devDependencies": {
28+
"concurrently": "^3.1.0"
29+
}
30+
}

0 commit comments

Comments
 (0)