diff --git a/examples/python/handoff.py b/examples/python/handoff.py new file mode 100644 index 0000000..7cc2a85 --- /dev/null +++ b/examples/python/handoff.py @@ -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:]) \ No newline at end of file diff --git a/examples/python/index.js b/examples/python/index.js new file mode 100644 index 0000000..93b50cc --- /dev/null +++ b/examples/python/index.js @@ -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 +})); \ No newline at end of file diff --git a/examples/python/package.json b/examples/python/package.json new file mode 100644 index 0000000..15cb063 --- /dev/null +++ b/examples/python/package.json @@ -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" + } +} diff --git a/examples/python/readme.md b/examples/python/readme.md new file mode 100644 index 0000000..62f0f6f --- /dev/null +++ b/examples/python/readme.md @@ -0,0 +1,43 @@ +# OpenBCI Node SDK to Python + +## About + +Written to end the struggles of python researchers and developers. ~ written with love by [Push The World!](http://www.pushtheworldllc.com) + +This module has every feature available on the OpenBCI Board. + +## Prerequisites + +* [Python 2.7](https://www.python.org/downloads/) +* [ZeroMQ](http://zeromq.org/bindings:python) + ```python + pip install pyzmq + ``` +* [Node.js LTS](https://nodejs.org/en/) + + +## Installation +For Python 2.7 do: +```bash +python setup.py install +``` +For Node: +```bash +npm install +``` + +## Running +``` +npm start +``` +Verbose: +``` +npm run start-verbose +``` +For running just the node, for example if you were running the python in a separate ide and debugging, it's useful. +```python +npm run start-node +``` + +## Contributing +Please PR if you have code to contribute! \ No newline at end of file diff --git a/examples/python/setup.py b/examples/python/setup.py new file mode 100644 index 0000000..559fee5 --- /dev/null +++ b/examples/python/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup, find_packages + +setup(name='openbci-node-python', + version='0.0.1', + description='Node to Python the right way', + url='', + author='AJ Keller', + author_email='pushtheworldllc@gmail.com', + license='MIT', + packages=find_packages(), + install_requires=['numpy', 'pyzmq'], + zip_safe=False) \ No newline at end of file diff --git a/package.json b/package.json index d0fad46..c6109b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openbci", - "version": "1.4.1", + "version": "1.4.2", "description": "The official Node.js SDK for the OpenBCI Biosensor Board.", "main": "openBCIBoard", "scripts": {