diff --git a/.travis.yml b/.travis.yml index cd882db..ec95018 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,10 @@ addons: install: # Install project requirements - pip install -r requirements.txt + # Install BluePy for Ganglion because Travis runs Linux + - pip install bluepy # Install test and coverage requirements - - pip install codecov mock nose coverage + - pip install codecov mock nose coverage pylint # Run tests script: @@ -30,3 +32,4 @@ script: - nosetests --with-coverage --cover-package=openbci after_success: - codecov + - pylint openbci diff --git a/README.md b/README.md index e2b7741..e53ddbc 100644 --- a/README.md +++ b/README.md @@ -114,17 +114,21 @@ NOTE: For comprehensive list see requirments.txt: (https://github.com/OpenBCI/Op OpenBCI 8 and 32 bit board with 8 or 16 channels. -This library includes the main open_bci_v3 class definition that instantiates an OpenBCI Board object. This object will initialize communication with the board and get the environment ready for data streaming. This library is designed to work with iOS and Linux distributions. To use a Windows OS, change the __init__ function in open_bci_v3.py to establish a serial connection in Windows. +This library includes the OpenBCICyton and OpenBCIGanglion classes which are drivers for their respective devices. The OpenBCICyton class is designed to work on all systems, while the OpenBCIGanglion class relies on a Bluetooth driver that is only available on Linux, discussed in the next section. For additional details on connecting your Cyton board visit: http://docs.openbci.com/Hardware/02-Cyton ### Ganglion Board -The Ganglion board relies on Bluetooth Low Energy connectivity (BLE). +The Ganglion board relies on Bluetooth Low Energy connectivity (BLE), and our code relies on the BluePy library to communicate with it. The BluePy library currently only works on Linux-based operating systems. To use Ganglion you will need to install it: -You may need to alter the settings of your bluetooth adapter in order to reduce latency and avoid packet drops -- e.g. if the terminal spams "Warning: Dropped 1 packets" several times a seconds, DO THAT. +`pip install bluepy` -On linux, assuming `hci0` is the name of your bluetooth adapter: +You may be able to use the Ganglion board from a virtual machine (VM) running Linux on other operating systems, such as MacOS or Windows. See [this thread](https://github.com/OpenBCI/OpenBCI_Python/issues/68) for advice. + +You may need to alter the settings of your Bluetooth adapter in order to reduce latency and avoid packet drops -- e.g. if the terminal spams "Warning: Dropped 1 packets" several times a seconds, DO THAT. + +On Linux, assuming `hci0` is the name of your bluetooth adapter: `sudo bash -c 'echo 9 > /sys/kernel/debug/bluetooth/hci0/conn_min_interval'` @@ -253,7 +257,6 @@ Warning: Connecting pins to high frequency 2x amp signal --> a Corresponding SD file OBCI_18.TXT$$$ --> /start T:3 - ``` NOTES: @@ -296,37 +299,37 @@ Note: type `/start` to launch the selected plugins. Add new functionalities to user.py by creating new scripts inside the `plugins` folder. You class must inherit from yapsy.IPlugin, see below a minimal example with `print` plugin: ```python - import plugin_interface as plugintypes +import plugin_interface as plugintypes - class PluginPrint(plugintypes.IPluginExtended): - def activate(self): - print "Print activated" +class PluginPrint(plugintypes.IPluginExtended): + def activate(self): + print("Print activated") - def deactivate(self): - print "Goodbye" + def deactivate(self): + print("Goodbye") - def show_help(self): - print "I do not need any parameter, just printing stuff." + def show_help(self): + print("I do not need any parameter, just printing stuff.") - # called with each new sample - def __call__(self, sample): - print "----------------" - print("%f" %(sample.id)) - print sample.channel_data - print sample.aux_data + # called with each new sample + def __call__(self, sample): + print("----------------") + print("%f" % sample.id) + print(sample.channel_data) + print(sample.aux_data) ``` Describe your plugin with a corresponding `print.yapsy-plugin`: ``` - [Core] - Name = print - Module = print - - [Documentation] - Author = Various - Version = 0.1 - Description = Print board values on stdout +[Core] +Name = print +Module = print + +[Documentation] +Author = Various +Version = 0.1 +Description = Print board values on stdout ``` diff --git a/externals/mne_openbci.py b/externals/mne_openbci.py index 2d12c33..a2a3752 100644 --- a/externals/mne_openbci.py +++ b/externals/mne_openbci.py @@ -5,6 +5,7 @@ # License: BSD (3-clause) import warnings + np = None try: import numpy as np @@ -18,6 +19,7 @@ except ImportError: raise ImportError('MNE is needed to use function.') + class RawOpenBCI(_BaseRaw): """Raw object from OpenBCI file @@ -59,6 +61,7 @@ class RawOpenBCI(_BaseRaw): -------- mne.io.Raw : Documentation of attribute and methods. """ + @verbose def __init__(self, input_fname, montage=None, eog=None, misc=(-3, -2, -1), stim_channel=None, scale=1e-6, sfreq=250, diff --git a/openbci/__init__.py b/openbci/__init__.py index 3717f85..173b1b3 100644 --- a/openbci/__init__.py +++ b/openbci/__init__.py @@ -1,9 +1,6 @@ - from .cyton import OpenBCICyton -from .ganglion import OpenBCIGanglion from .plugins import * from .utils import * from .wifi import OpenBCIWiFi - - -__version__ = "1.0.0" +if sys.platform.startswith("linux"): + from .ganglion import OpenBCIGanglion diff --git a/openbci/cyton.py b/openbci/cyton.py index 3805ca6..d0a182c 100644 --- a/openbci/cyton.py +++ b/openbci/cyton.py @@ -10,12 +10,14 @@ def handle_sample(sample): board.print_register_settings() board.start_streaming(handle_sample) -NOTE: If daisy modules is enabled, the callback will occur every two samples, hence "packet_id" will only contain even numbers. As a side effect, the sampling rate will be divided by 2. +NOTE: If daisy modules is enabled, the callback will occur every two samples, hence "packet_id" + will only contain even numbers. As a side effect, the sampling rate will be divided by 2. FIXME: at the moment we can just force daisy mode, do not check that the module is detected. TODO: enable impedance """ +from __future__ import print_function import serial import struct import numpy as np @@ -31,10 +33,12 @@ def handle_sample(sample): SAMPLE_RATE = 250.0 # Hz START_BYTE = 0xA0 # start of data packet END_BYTE = 0xC0 # end of data packet -ADS1299_Vref = 4.5 #reference voltage for ADC in ADS1299. set by its hardware -ADS1299_gain = 24.0 #assumed gain setting for ADS1299. set by its Arduino code -scale_fac_uVolts_per_count = ADS1299_Vref/float((pow(2,23)-1))/ADS1299_gain*1000000. -scale_fac_accel_G_per_count = 0.002 /(pow(2,4)) #assume set to +/4G, so 2 mG +ADS1299_Vref = 4.5 # reference voltage for ADC in ADS1299. set by its hardware +ADS1299_gain = 24.0 # assumed gain setting for ADS1299. set by its Arduino code +scale_fac_uVolts_per_count = ADS1299_Vref / \ + float((pow(2, 23) - 1)) / ADS1299_gain * 1000000. +scale_fac_accel_G_per_count = 0.002 / \ + (pow(2, 4)) # assume set to +/4G, so 2 mG ''' #Commands for in SDK http://docs.openbci.com/software/01-Open BCI_SDK: @@ -57,561 +61,575 @@ def handle_sample(sample): class OpenBCICyton(object): - """ - - Handle a connection to an OpenBCI board. - - Args: - port: The port to connect to. - baud: The baud of the serial connection. - daisy: Enable or disable daisy module and 16 chans readings - aux, impedance: unused, for compatibility with ganglion API - """ - - def __init__(self, port=None, baud=115200, filter_data=True, - scaled_output=True, daisy=False, aux=False, impedance=False, log=True, timeout=None): - self.log = log # print_incoming_text needs log - self.streaming = False - self.baudrate = baud - self.timeout = timeout - if not port: - port = self.find_port() - self.port = port - # might be handy to know API - self.board_type = "cyton" - print("Connecting to V3 at port %s" %(port)) - if port == "loop://": - # For testing purposes - self.ser = serial.serial_for_url(port, baudrate=baud, timeout=timeout) - else: - self.ser = serial.Serial(port= port, baudrate = baud, timeout=timeout) - - print("Serial established...") - - time.sleep(2) - #Initialize 32-bit board, doesn't affect 8bit board - self.ser.write(b'v'); - - #wait for device to be ready - time.sleep(1) - if port != "loop://": - self.print_incoming_text() - - self.streaming = False - self.filtering_data = filter_data - self.scaling_output = scaled_output - self.eeg_channels_per_sample = 8 # number of EEG channels per sample *from the board* - self.aux_channels_per_sample = 3 # number of AUX channels per sample *from the board* - self.imp_channels_per_sample = 0 # impedance check not supported at the moment - self.read_state = 0 - self.daisy = daisy - self.last_odd_sample = OpenBCISample(-1, [], []) # used for daisy - self.log_packet_count = 0 - self.attempt_reconnect = False - self.last_reconnect = 0 - self.reconnect_freq = 5 - self.packets_dropped = 0 - - #Disconnects from board when terminated - atexit.register(self.disconnect) - - def getBoardType(self): - """ Returns the version of the board """ - return self.board_type - - def setImpedance(self, flag): - """ Enable/disable impedance measure. Not implemented at the moment on Cyton. """ - return - - def ser_write(self, b): - """Access serial port object for write""" - self.ser.write(b) - - def ser_read(self): - """Access serial port object for read""" - return self.ser.read() - - def ser_inWaiting(self): - """Access serial port object for inWaiting""" - return self.ser.inWaiting(); - - def getSampleRate(self): - if self.daisy: - return SAMPLE_RATE/2 - else: - return SAMPLE_RATE - - def getNbEEGChannels(self): - if self.daisy: - return self.eeg_channels_per_sample*2 - else: - return self.eeg_channels_per_sample - - def getNbAUXChannels(self): - return self.aux_channels_per_sample - - def getNbImpChannels(self): - return self.imp_channels_per_sample - - def start_streaming(self, callback, lapse=-1): """ - Start handling streaming data from the board. Call a provided callback - for every single sample that is processed (every two samples with daisy module). + Handle a connection to an OpenBCI board. Args: - callback: A callback function -- or a list of functions -- that will receive a single argument of the - OpenBCISample object captured. + port: The port to connect to. + baud: The baud of the serial connection. + daisy: Enable or disable daisy module and 16 chans readings + aux, impedance: unused, for compatibility with ganglion API """ - if not self.streaming: - self.ser.write(b'b') - self.streaming = True - - start_time = timeit.default_timer() - - # Enclose callback funtion in a list if it comes alone - if not isinstance(callback, list): - callback = [callback] - - - #Initialize check connection - self.check_connection() - - while self.streaming: - - # read current sample - sample = self._read_serial_binary() - # if a daisy module is attached, wait to concatenate two samples (main board + daisy) before passing it to callback - if self.daisy: - # odd sample: daisy sample, save for later - if ~sample.id % 2: - self.last_odd_sample = sample - # even sample: concatenate and send if last sample was the fist part, otherwise drop the packet - elif sample.id - 1 == self.last_odd_sample.id: - # the aux data will be the average between the two samples, as the channel samples themselves have been averaged by the board - avg_aux_data = list((np.array(sample.aux_data) + np.array(self.last_odd_sample.aux_data))/2) - whole_sample = OpenBCISample(sample.id, sample.channel_data + self.last_odd_sample.channel_data, avg_aux_data) - for call in callback: - call(whole_sample) - else: - for call in callback: - call(sample) - - if(lapse > 0 and timeit.default_timer() - start_time > lapse): - self.stop(); - if self.log: - self.log_packet_count = self.log_packet_count + 1; - - - """ - PARSER: - Parses incoming data packet into OpenBCISample. - Incoming Packet Structure: - Start Byte(1)|Sample ID(1)|Channel Data(24)|Aux Data(6)|End Byte(1) - 0xA0|0-255|8, 3-byte signed ints|3 2-byte signed ints|0xC0 - - """ - def _read_serial_binary(self, max_bytes_to_skip=3000): - def read(n): - bb = self.ser.read(n) - if not bb: - self.warn('Device appears to be stalled. Quitting...') - sys.exit() - raise Exception('Device Stalled') - sys.exit() - return '\xFF' - else: - return bb - - for rep in range(max_bytes_to_skip): - - #---------Start Byte & ID--------- - if self.read_state == 0: - - b = read(1) - - if struct.unpack('B', b)[0] == START_BYTE: - if(rep != 0): - self.warn('Skipped %d bytes before start found' %(rep)) - rep = 0; - packet_id = struct.unpack('B', read(1))[0] #packet id goes from 0-255 - log_bytes_in = str(packet_id); - - self.read_state = 1 - - #---------Channel Data--------- - elif self.read_state == 1: - channel_data = [] - for c in range(self.eeg_channels_per_sample): - - # 3 byte ints - literal_read = read(3) - - unpacked = struct.unpack('3B', literal_read) - log_bytes_in = log_bytes_in + '|' + str(literal_read) - - # 3byte int in 2s compliment - if (unpacked[0] > 127): - pre_fix = bytes(bytearray.fromhex('FF')) - else: - pre_fix = bytes(bytearray.fromhex('00')) - - - literal_read = pre_fix + literal_read - - # unpack little endian(>) signed integer(i) (makes unpacking platform independent) - myInt = struct.unpack('>i', literal_read)[0] - - if self.scaling_output: - channel_data.append(myInt*scale_fac_uVolts_per_count) - else: - channel_data.append(myInt) - - self.read_state = 2 - - #---------Accelerometer Data--------- - elif self.read_state == 2: - aux_data = [] - for a in range(self.aux_channels_per_sample): - - #short = h - acc = struct.unpack('>h', read(2))[0] - log_bytes_in = log_bytes_in + '|' + str(acc); - - if self.scaling_output: - aux_data.append(acc*scale_fac_accel_G_per_count) - else: - aux_data.append(acc) - - self.read_state = 3; - #---------End Byte--------- - elif self.read_state == 3: - val = struct.unpack('B', read(1))[0] - log_bytes_in = log_bytes_in + '|' + str(val); - self.read_state = 0 #read next packet - if (val == END_BYTE): - sample = OpenBCISample(packet_id, channel_data, aux_data) - self.packets_dropped = 0 - return sample + + def __init__(self, port=None, baud=115200, filter_data=True, scaled_output=True, + daisy=False, aux=False, impedance=False, log=True, timeout=None): + self.log = log # print_incoming_text needs log + self.streaming = False + self.baudrate = baud + self.timeout = timeout + if not port: + port = self.find_port() + self.port = port + # might be handy to know API + self.board_type = "cyton" + print("Connecting to V3 at port %s" % (port)) + if port == "loop://": + # For testing purposes + self.ser = serial.serial_for_url(port, baudrate=baud, timeout=timeout) + else: + self.ser = serial.Serial(port=port, baudrate=baud, timeout=timeout) + + print("Serial established...") + + time.sleep(2) + # Initialize 32-bit board, doesn't affect 8bit board + self.ser.write(b'v') + + # wait for device to be ready + time.sleep(1) + if port != "loop://": + self.print_incoming_text() + + self.streaming = False + self.filtering_data = filter_data + self.scaling_output = scaled_output + # number of EEG channels per sample *from the board* + self.eeg_channels_per_sample = 8 + # number of AUX channels per sample *from the board* + self.aux_channels_per_sample = 3 + self.imp_channels_per_sample = 0 # impedance check not supported at the moment + self.read_state = 0 + self.daisy = daisy + self.last_odd_sample = OpenBCISample(-1, [], []) # used for daisy + self.log_packet_count = 0 + self.attempt_reconnect = False + self.last_reconnect = 0 + self.reconnect_freq = 5 + self.packets_dropped = 0 + + # Disconnects from board when terminated + atexit.register(self.disconnect) + + def getBoardType(self): + """ Returns the version of the board """ + return self.board_type + + def setImpedance(self, flag): + """ Enable/disable impedance measure. Not implemented at the moment on Cyton. """ + return + + def ser_write(self, b): + """Access serial port object for write""" + self.ser.write(b) + + def ser_read(self): + """Access serial port object for read""" + return self.ser.read() + + def ser_inWaiting(self): + """Access serial port object for inWaiting""" + return self.ser.inWaiting() + + def getSampleRate(self): + if self.daisy: + return SAMPLE_RATE / 2 else: - self.warn("ID:<%d> instead of <%s>" - %(packet_id, val, END_BYTE)) - logging.debug(log_bytes_in); - self.packets_dropped = self.packets_dropped + 1 + return SAMPLE_RATE + + def getNbEEGChannels(self): + if self.daisy: + return self.eeg_channels_per_sample * 2 + else: + return self.eeg_channels_per_sample + + def getNbAUXChannels(self): + return self.aux_channels_per_sample + + def getNbImpChannels(self): + return self.imp_channels_per_sample + + def start_streaming(self, callback, lapse=-1): + """ + Start handling streaming data from the board. Call a provided callback + for every single sample that is processed (every two samples with daisy module). + + Args: + callback: A callback function, or a list of functions, that will receive a single + argument of the OpenBCISample object captured. + """ + if not self.streaming: + self.ser.write(b'b') + self.streaming = True + + start_time = timeit.default_timer() + + # Enclose callback funtion in a list if it comes alone + if not isinstance(callback, list): + callback = [callback] + + # Initialize check connection + self.check_connection() + + while self.streaming: + + # read current sample + sample = self._read_serial_binary() + # if a daisy module is attached, wait to concatenate two samples + # (main board + daisy) before passing it to callback + if self.daisy: + # odd sample: daisy sample, save for later + if ~sample.id % 2: + self.last_odd_sample = sample + # even sample: concatenate and send if last sample was the fist part, + # otherwise drop the packet + elif sample.id - 1 == self.last_odd_sample.id: + # the aux data will be the average between the two samples, as the channel + # samples themselves have been averaged by the board + avg_aux_data = list( + (np.array(sample.aux_data) + np.array(self.last_odd_sample.aux_data)) / 2) + whole_sample = OpenBCISample(sample.id, + sample.channel_data + + self.last_odd_sample.channel_data, + avg_aux_data) + for call in callback: + call(whole_sample) + else: + for call in callback: + call(sample) + + if lapse > 0 and (timeit.default_timer() - start_time) > lapse: + self.stop() + if self.log: + self.log_packet_count = self.log_packet_count + 1 + + """ + PARSER: + Parses incoming data packet into OpenBCISample. + Incoming Packet Structure: + Start Byte(1)|Sample ID(1)|Channel Data(24)|Aux Data(6)|End Byte(1) + 0xA0|0-255|8, 3-byte signed ints|3 2-byte signed ints|0xC0 - """ - - Clean Up (atexit) - - """ - def stop(self): - print("Stopping streaming...\nWait for buffer to flush...") - self.streaming = False - self.ser.write(b's') - if self.log: - logging.warning('sent : stopped streaming') - - def disconnect(self): - if(self.streaming == True): - self.stop() - if (self.ser.isOpen()): - print("Closing Serial...") - self.ser.close() - logging.warning('serial closed') - - - """ - - SETTINGS AND HELPERS - - """ - def warn(self, text): - if self.log: - #log how many packets where sent succesfully in between warnings - if self.log_packet_count: - logging.info('Data packets received:'+str(self.log_packet_count)) - self.log_packet_count = 0; - logging.warning(text) - print("Warning: %s" % text) - - - def print_incoming_text(self): """ - When starting the connection, print all the debug data until - we get to a line with the end sequence '$$$'. + def _read_serial_binary(self, max_bytes_to_skip=3000): + def read(n): + bb = self.ser.read(n) + if not bb: + self.warn('Device appears to be stalled. Quitting...') + sys.exit() + raise Exception('Device Stalled') + sys.exit() + return '\xFF' + else: + return bb + + for rep in range(max_bytes_to_skip): + + # ---------Start Byte & ID--------- + if self.read_state == 0: + + b = read(1) + + if struct.unpack('B', b)[0] == START_BYTE: + if (rep != 0): + self.warn( + 'Skipped %d bytes before start found' % (rep)) + rep = 0 + # packet id goes from 0-255 + packet_id = struct.unpack('B', read(1))[0] + log_bytes_in = str(packet_id) + + self.read_state = 1 + + # ---------Channel Data--------- + elif self.read_state == 1: + channel_data = [] + for c in range(self.eeg_channels_per_sample): + + # 3 byte ints + literal_read = read(3) + + unpacked = struct.unpack('3B', literal_read) + log_bytes_in = log_bytes_in + '|' + str(literal_read) + + # 3byte int in 2s compliment + if (unpacked[0] > 127): + pre_fix = bytes(bytearray.fromhex('FF')) + else: + pre_fix = bytes(bytearray.fromhex('00')) + + literal_read = pre_fix + literal_read + + # unpack little endian(>) signed integer(i) + # (makes unpacking platform independent) + myInt = struct.unpack('>i', literal_read)[0] + + if self.scaling_output: + channel_data.append(myInt * scale_fac_uVolts_per_count) + else: + channel_data.append(myInt) + + self.read_state = 2 + + # ---------Accelerometer Data--------- + elif self.read_state == 2: + aux_data = [] + for a in range(self.aux_channels_per_sample): + + # short = h + acc = struct.unpack('>h', read(2))[0] + log_bytes_in = log_bytes_in + '|' + str(acc) + + if self.scaling_output: + aux_data.append(acc * scale_fac_accel_G_per_count) + else: + aux_data.append(acc) + + self.read_state = 3 + # ---------End Byte--------- + elif self.read_state == 3: + val = struct.unpack('B', read(1))[0] + log_bytes_in = log_bytes_in + '|' + str(val) + self.read_state = 0 # read next packet + if (val == END_BYTE): + sample = OpenBCISample(packet_id, channel_data, aux_data) + self.packets_dropped = 0 + return sample + else: + self.warn("ID:<%d> instead of <%s>" + % (packet_id, val, END_BYTE)) + logging.debug(log_bytes_in) + self.packets_dropped = self.packets_dropped + 1 """ - line = '' - #Wait for device to send data - time.sleep(1) - - if self.ser.inWaiting(): - line = '' - c = '' - #Look for end sequence $$$ - while '$$$' not in line: - c = self.ser.read().decode('utf-8', errors='replace') # we're supposed to get UTF8 text, but the board might behave otherwise - line += c - print(line); - else: - self.warn("No Message") - - def openbci_id(self, serial): + + Clean Up (atexit) + """ - When automatically detecting port, parse the serial return for the "OpenBCI" ID. + def stop(self): + print("Stopping streaming...\nWait for buffer to flush...") + self.streaming = False + self.ser.write(b's') + if self.log: + logging.warning('sent : stopped streaming') + + def disconnect(self): + if (self.streaming == True): + self.stop() + if (self.ser.isOpen()): + print("Closing Serial...") + self.ser.close() + logging.warning('serial closed') """ - line = '' - #Wait for device to send data - time.sleep(2) - - if serial.inWaiting(): - line = '' - c = '' - #Look for end sequence $$$ - while '$$$' not in line: - c = serial.read().decode('utf-8', errors='replace') # we're supposed to get UTF8 text, but the board might behave otherwise - line += c - if "OpenBCI" in line: - return True - return False - - def print_register_settings(self): - self.ser.write(b'?') - time.sleep(0.5) - self.print_incoming_text(); - #DEBBUGING: Prints individual incoming bytes - def print_bytes_in(self): - if not self.streaming: - self.ser.write(b'b') - self.streaming = True - while self.streaming: - print(struct.unpack('B',self.ser.read())[0]); - - '''Incoming Packet Structure: - Start Byte(1)|Sample ID(1)|Channel Data(24)|Aux Data(6)|End Byte(1) - 0xA0|0-255|8, 3-byte signed ints|3 2-byte signed ints|0xC0''' - - def print_packets_in(self): - while self.streaming: - b = struct.unpack('B', self.ser.read())[0]; - - if b == START_BYTE: - self.attempt_reconnect = False - if skipped_str: - logging.debug('SKIPPED\n' + skipped_str + '\nSKIPPED') - skipped_str = '' - - packet_str = "%03d"%(b) + '|'; - b = struct.unpack('B', self.ser.read())[0]; - packet_str = packet_str + "%03d"%(b) + '|'; - - #data channels - for i in range(24-1): - b = struct.unpack('B', self.ser.read())[0]; - packet_str = packet_str + '.' + "%03d"%(b); - - b = struct.unpack('B', self.ser.read())[0]; - packet_str = packet_str + '.' + "%03d"%(b) + '|'; - - #aux channels - for i in range(6-1): - b = struct.unpack('B', self.ser.read())[0]; - packet_str = packet_str + '.' + "%03d"%(b); - - b = struct.unpack('B', self.ser.read())[0]; - packet_str = packet_str + '.' + "%03d"%(b) + '|'; - - #end byte - b = struct.unpack('B', self.ser.read())[0]; - - #Valid Packet - if b == END_BYTE: - packet_str = packet_str + '.' + "%03d"%(b) + '|VAL'; - print(packet_str) - #logging.debug(packet_str) - - #Invalid Packet + + SETTINGS AND HELPERS + + """ + + def warn(self, text): + if self.log: + # log how many packets where sent succesfully in between warnings + if self.log_packet_count: + logging.info('Data packets received:' + + str(self.log_packet_count)) + self.log_packet_count = 0 + logging.warning(text) + print("Warning: %s" % text) + + def print_incoming_text(self): + """ + + When starting the connection, print all the debug data until + we get to a line with the end sequence '$$$'. + + """ + line = '' + # Wait for device to send data + time.sleep(1) + + if self.ser.inWaiting(): + line = '' + c = '' + # Look for end sequence $$$ + while '$$$' not in line: + # we're supposed to get UTF8 text, but the board might behave otherwise + c = self.ser.read().decode('utf-8', + errors='replace') + line += c + print(line) + else: + self.warn("No Message") + + def openbci_id(self, serial): + """ + + When automatically detecting port, parse the serial return for the "OpenBCI" ID. + + """ + line = '' + # Wait for device to send data + time.sleep(2) + + if serial.inWaiting(): + line = '' + c = '' + # Look for end sequence $$$ + while '$$$' not in line: + # we're supposed to get UTF8 text, but the board might behave otherwise + c = serial.read().decode('utf-8', + errors='replace') + line += c + if "OpenBCI" in line: + return True + return False + + def print_register_settings(self): + self.ser.write(b'?') + time.sleep(0.5) + self.print_incoming_text() + + # DEBBUGING: Prints individual incoming bytes + def print_bytes_in(self): + if not self.streaming: + self.ser.write(b'b') + self.streaming = True + while self.streaming: + print(struct.unpack('B', self.ser.read())[0]) + + '''Incoming Packet Structure: + Start Byte(1)|Sample ID(1)|Channel Data(24)|Aux Data(6)|End Byte(1) + 0xA0|0-255|8, 3-byte signed ints|3 2-byte signed ints|0xC0''' + + def print_packets_in(self): + while self.streaming: + b = struct.unpack('B', self.ser.read())[0] + + if b == START_BYTE: + self.attempt_reconnect = False + if skipped_str: + logging.debug('SKIPPED\n' + skipped_str + '\nSKIPPED') + skipped_str = '' + + packet_str = "%03d" % (b) + '|' + b = struct.unpack('B', self.ser.read())[0] + packet_str = packet_str + "%03d" % (b) + '|' + + # data channels + for i in range(24 - 1): + b = struct.unpack('B', self.ser.read())[0] + packet_str = packet_str + '.' + "%03d" % (b) + + b = struct.unpack('B', self.ser.read())[0] + packet_str = packet_str + '.' + "%03d" % (b) + '|' + + # aux channels + for i in range(6 - 1): + b = struct.unpack('B', self.ser.read())[0] + packet_str = packet_str + '.' + "%03d" % (b) + + b = struct.unpack('B', self.ser.read())[0] + packet_str = packet_str + '.' + "%03d" % (b) + '|' + + # end byte + b = struct.unpack('B', self.ser.read())[0] + + # Valid Packet + if b == END_BYTE: + packet_str = packet_str + '.' + "%03d" % (b) + '|VAL' + print(packet_str) + # logging.debug(packet_str) + + # Invalid Packet + else: + packet_str = packet_str + '.' + "%03d" % (b) + '|INV' + # Reset + self.attempt_reconnect = True + + else: + print(b) + if b == END_BYTE: + skipped_str = skipped_str + '|END|' + else: + skipped_str = skipped_str + "%03d" % (b) + '.' + + if self.attempt_reconnect and \ + (timeit.default_timer() - self.last_reconnect) > self.reconnect_freq: + self.last_reconnect = timeit.default_timer() + self.warn('Reconnecting') + self.reconnect() + + def check_connection(self, interval=2, max_packets_to_skip=10): + # stop checking when we're no longer streaming + if not self.streaming: + return + # check number of dropped packages and establish connection problem if too large + if self.packets_dropped > max_packets_to_skip: + # if error, attempt to reconnect + self.reconnect() + # check again again in 2 seconds + threading.Timer(interval, self.check_connection).start() + + def reconnect(self): + self.packets_dropped = 0 + self.warn('Reconnecting') + self.stop() + time.sleep(0.5) + self.ser.write(b'v') + time.sleep(0.5) + self.ser.write(b'b') + time.sleep(0.5) + self.streaming = True + # self.attempt_reconnect = False + + # Adds a filter at 60hz to cancel out ambient electrical noise + def enable_filters(self): + self.ser.write(b'f') + self.filtering_data = True + + def disable_filters(self): + self.ser.write(b'g') + self.filtering_data = False + + def test_signal(self, signal): + """ Enable / disable test signal """ + if signal == 0: + self.ser.write(b'0') + self.warn("Connecting all pins to ground") + elif signal == 1: + self.ser.write(b'p') + self.warn("Connecting all pins to Vcc") + elif signal == 2: + self.ser.write(b'-') + self.warn("Connecting pins to low frequency 1x amp signal") + elif signal == 3: + self.ser.write(b'=') + self.warn("Connecting pins to high frequency 1x amp signal") + elif signal == 4: + self.ser.write(b'[') + self.warn("Connecting pins to low frequency 2x amp signal") + elif signal == 5: + self.ser.write(b']') + self.warn("Connecting pins to high frequency 2x amp signal") else: - packet_str = packet_str + '.' + "%03d"%(b) + '|INV'; - #Reset - self.attempt_reconnect = True - - - else: - print(b) - if b == END_BYTE: - skipped_str = skipped_str + '|END|' + self.warn("%s is not a known test signal. Valid signals go from 0-5" % signal) + + def set_channel(self, channel, toggle_position): + """ Enable / disable channels """ + # Commands to set toggle to on position + if toggle_position == 1: + if channel is 1: + self.ser.write(b'!') + if channel is 2: + self.ser.write(b'@') + if channel is 3: + self.ser.write(b'#') + if channel is 4: + self.ser.write(b'$') + if channel is 5: + self.ser.write(b'%') + if channel is 6: + self.ser.write(b'^') + if channel is 7: + self.ser.write(b'&') + if channel is 8: + self.ser.write(b'*') + if channel is 9 and self.daisy: + self.ser.write(b'Q') + if channel is 10 and self.daisy: + self.ser.write(b'W') + if channel is 11 and self.daisy: + self.ser.write(b'E') + if channel is 12 and self.daisy: + self.ser.write(b'R') + if channel is 13 and self.daisy: + self.ser.write(b'T') + if channel is 14 and self.daisy: + self.ser.write(b'Y') + if channel is 15 and self.daisy: + self.ser.write(b'U') + if channel is 16 and self.daisy: + self.ser.write(b'I') + # Commands to set toggle to off position + elif toggle_position == 0: + if channel is 1: + self.ser.write(b'1') + if channel is 2: + self.ser.write(b'2') + if channel is 3: + self.ser.write(b'3') + if channel is 4: + self.ser.write(b'4') + if channel is 5: + self.ser.write(b'5') + if channel is 6: + self.ser.write(b'6') + if channel is 7: + self.ser.write(b'7') + if channel is 8: + self.ser.write(b'8') + if channel is 9 and self.daisy: + self.ser.write(b'q') + if channel is 10 and self.daisy: + self.ser.write(b'w') + if channel is 11 and self.daisy: + self.ser.write(b'e') + if channel is 12 and self.daisy: + self.ser.write(b'r') + if channel is 13 and self.daisy: + self.ser.write(b't') + if channel is 14 and self.daisy: + self.ser.write(b'y') + if channel is 15 and self.daisy: + self.ser.write(b'u') + if channel is 16 and self.daisy: + self.ser.write(b'i') + + def find_port(self): + # Finds the serial port names + if sys.platform.startswith('win'): + ports = ['COM%s' % (i + 1) for i in range(256)] + elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'): + ports = glob.glob('/dev/ttyUSB*') + elif sys.platform.startswith('darwin'): + ports = glob.glob('/dev/tty.usbserial*') else: - skipped_str = skipped_str + "%03d"%(b) + '.' + raise EnvironmentError('Error finding ports on your operating system') + openbci_port = '' + for port in ports: + try: + s = serial.Serial(port=port, baudrate=self.baudrate, timeout=self.timeout) + s.write(b'v') + openbci_serial = self.openbci_id(s) + s.close() + if openbci_serial: + openbci_port = port + except (OSError, serial.SerialException): + pass + if openbci_port == '': + raise OSError('Cannot find OpenBCI port') + else: + return openbci_port - if self.attempt_reconnect and (timeit.default_timer()-self.last_reconnect) > self.reconnect_freq: - self.last_reconnect = timeit.default_timer() - self.warn('Reconnecting') - self.reconnect() - - - - def check_connection(self, interval = 2, max_packets_to_skip=10): - # stop checking when we're no longer streaming - if not self.streaming: - return - #check number of dropped packages and establish connection problem if too large - if self.packets_dropped > max_packets_to_skip: - #if error, attempt to reconect - self.reconnect() - # check again again in 2 seconds - threading.Timer(interval, self.check_connection).start() - - def reconnect(self): - self.packets_dropped = 0 - self.warn('Reconnecting') - self.stop() - time.sleep(0.5) - self.ser.write(b'v') - time.sleep(0.5) - self.ser.write(b'b') - time.sleep(0.5) - self.streaming = True - #self.attempt_reconnect = False - - - #Adds a filter at 60hz to cancel out ambient electrical noise - def enable_filters(self): - self.ser.write(b'f') - self.filtering_data = True; - - def disable_filters(self): - self.ser.write(b'g') - self.filtering_data = False; - - def test_signal(self, signal): - """ Enable / disable test signal """ - if signal == 0: - self.ser.write(b'0') - self.warn("Connecting all pins to ground") - elif signal == 1: - self.ser.write(b'p') - self.warn("Connecting all pins to Vcc") - elif signal == 2: - self.ser.write(b'-') - self.warn("Connecting pins to low frequency 1x amp signal") - elif signal == 3: - self.ser.write(b'=') - self.warn("Connecting pins to high frequency 1x amp signal") - elif signal == 4: - self.ser.write(b'[') - self.warn("Connecting pins to low frequency 2x amp signal") - elif signal == 5: - self.ser.write(b']') - self.warn("Connecting pins to high frequency 2x amp signal") - else: - self.warn("%s is not a known test signal. Valid signals go from 0-5" %(signal)) - - def set_channel(self, channel, toggle_position): - """ Enable / disable channels """ - #Commands to set toggle to on position - if toggle_position == 1: - if channel is 1: - self.ser.write(b'!') - if channel is 2: - self.ser.write(b'@') - if channel is 3: - self.ser.write(b'#') - if channel is 4: - self.ser.write(b'$') - if channel is 5: - self.ser.write(b'%') - if channel is 6: - self.ser.write(b'^') - if channel is 7: - self.ser.write(b'&') - if channel is 8: - self.ser.write(b'*') - if channel is 9 and self.daisy: - self.ser.write(b'Q') - if channel is 10 and self.daisy: - self.ser.write(b'W') - if channel is 11 and self.daisy: - self.ser.write(b'E') - if channel is 12 and self.daisy: - self.ser.write(b'R') - if channel is 13 and self.daisy: - self.ser.write(b'T') - if channel is 14 and self.daisy: - self.ser.write(b'Y') - if channel is 15 and self.daisy: - self.ser.write(b'U') - if channel is 16 and self.daisy: - self.ser.write(b'I') - #Commands to set toggle to off position - elif toggle_position == 0: - if channel is 1: - self.ser.write(b'1') - if channel is 2: - self.ser.write(b'2') - if channel is 3: - self.ser.write(b'3') - if channel is 4: - self.ser.write(b'4') - if channel is 5: - self.ser.write(b'5') - if channel is 6: - self.ser.write(b'6') - if channel is 7: - self.ser.write(b'7') - if channel is 8: - self.ser.write(b'8') - if channel is 9 and self.daisy: - self.ser.write(b'q') - if channel is 10 and self.daisy: - self.ser.write(b'w') - if channel is 11 and self.daisy: - self.ser.write(b'e') - if channel is 12 and self.daisy: - self.ser.write(b'r') - if channel is 13 and self.daisy: - self.ser.write(b't') - if channel is 14 and self.daisy: - self.ser.write(b'y') - if channel is 15 and self.daisy: - self.ser.write(b'u') - if channel is 16 and self.daisy: - self.ser.write(b'i') - - def find_port(self): - # Finds the serial port names - if sys.platform.startswith('win'): - ports = ['COM%s' % (i+1) for i in range(256)] - elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'): - ports = glob.glob('/dev/ttyUSB*') - elif sys.platform.startswith('darwin'): - ports = glob.glob('/dev/tty.usbserial*') - else: - raise EnvironmentError('Error finding ports on your operating system') - openbci_port = '' - for port in ports: - try: - s = serial.Serial(port= port, baudrate = self.baudrate, timeout=self.timeout) - s.write(b'v') - openbci_serial = self.openbci_id(s) - s.close() - if openbci_serial: - openbci_port = port; - except (OSError, serial.SerialException): - pass - if openbci_port == '': - raise OSError('Cannot find OpenBCI port') - else: - return openbci_port class OpenBCISample(object): - """Object encapulsating a single sample from the OpenBCI board. NB: dummy imp for plugin compatiblity""" - def __init__(self, packet_id, channel_data, aux_data): - self.id = packet_id - self.channel_data = channel_data - self.aux_data = aux_data - self.imp_data = [] - + """Object encapulsating a single sample from the OpenBCI board. + NB: dummy imp for plugin compatiblity + """ + def __init__(self, packet_id, channel_data, aux_data): + self.id = packet_id + self.channel_data = channel_data + self.aux_data = aux_data + self.imp_data = [] diff --git a/openbci/ganglion.py b/openbci/ganglion.py index b6aa6b9..959e334 100644 --- a/openbci/ganglion.py +++ b/openbci/ganglion.py @@ -14,6 +14,7 @@ def handle_sample(sample): TODO: support impedance TODO: reset board with 'v'? """ +from __future__ import print_function import struct import time import timeit @@ -25,7 +26,6 @@ def handle_sample(sample): import glob from bluepy.btle import Scanner, DefaultDelegate, Peripheral - SAMPLE_RATE = 200.0 # Hz scale_fac_uVolts_per_count = 1200 / (8388607.0 * 1.5 * 51.0) scale_fac_accel_G_per_count = 0.000016 @@ -46,768 +46,811 @@ def handle_sample(sample): class OpenBCIGanglion(object): - """ - Handle a connection to an OpenBCI board. - - Args: - port: MAC address of the Ganglion Board. "None" to attempt auto-detect. - aux: enable on not aux channels (i.e. switch to 18bit mode if set) - impedance: measures impedance when start streaming - timeout: in seconds, if set will try to disconnect / reconnect after a period without new data -- should be high if impedance check - max_packets_to_skip: will try to disconnect / reconnect after too many packets are skipped - baud, filter_data, daisy: Not used, for compatibility with v3 - """ - - def __init__(self, port=None, baud=0, filter_data=False, - scaled_output=True, daisy=False, log=True, aux=False, impedance=False, timeout=2, max_packets_to_skip=20): - # unused, for compatibility with Cyton v3 API - self.daisy = False - # these one are used - self.log = log # print_incoming_text needs log - self.aux = aux - self.streaming = False - self.timeout = timeout - self.max_packets_to_skip = max_packets_to_skip - self.scaling_output = scaled_output - self.impedance = impedance - - # might be handy to know API - self.board_type = "ganglion" - - print("Looking for Ganglion board") - if port == None: - port = self.find_port() - self.port = port # find_port might not return string - - self.connect() - - self.streaming = False - # number of EEG channels and (optionally) accelerometer channel - self.eeg_channels_per_sample = 4 - self.aux_channels_per_sample = 3 - self.imp_channels_per_sample = 5 - self.read_state = 0 - self.log_packet_count = 0 - self.packets_dropped = 0 - self.time_last_packet = 0 - - # Disconnects from board when terminated - atexit.register(self.disconnect) - - def getBoardType(self): - """ Returns the version of the board """ - return self.board_type - - def setImpedance(self, flag): - """ Enable/disable impedance measure """ - self.impedance = bool(flag) - - def connect(self): - """ Connect to the board and configure it. Note: recreates various objects upon call. """ - print ("Init BLE connection with MAC: " + self.port) - print ("NB: if it fails, try with root privileges.") - self.gang = Peripheral(self.port, 'random') # ADDR_TYPE_RANDOM - - print ("Get mainservice...") - self.service = self.gang.getServiceByUUID(BLE_SERVICE) - print ("Got:" + str(self.service)) - - print ("Get characteristics...") - self.char_read = self.service.getCharacteristics(BLE_CHAR_RECEIVE)[0] - print ("receive, properties: " + str(self.char_read.propertiesToString()) + ", supports read: " + str(self.char_read.supportsRead())) - - self.char_write = self.service.getCharacteristics(BLE_CHAR_SEND)[0] - print ("write, properties: " + str(self.char_write.propertiesToString()) + ", supports read: " + str(self.char_write.supportsRead())) - - self.char_discon = self.service.getCharacteristics(BLE_CHAR_DISCONNECT)[0] - print ("disconnect, properties: " + str(self.char_discon.propertiesToString()) + ", supports read: " + str(self.char_discon.supportsRead())) - - # set delegate to handle incoming data - self.delegate = GanglionDelegate(self.scaling_output) - self.gang.setDelegate(self.delegate) - - # enable AUX channel - if self.aux: - print("Enabling AUX data...") - try: - self.ser_write(b'n') - except Exception as e: - print("Something went wrong while enabling aux channels: " + str(e)) - - print("Turn on notifications") - # nead up-to-date bluepy, cf https://github.com/IanHarvey/bluepy/issues/53 - self.desc_notify = self.char_read.getDescriptors(forUUID=0x2902)[0] - try: - self.desc_notify.write(b"\x01") - except Exception as e: - print("Something went wrong while trying to enable notification: " + str(e)) - - print("Connection established") - - def init_streaming(self): - """ Tell the board to record like crazy. """ - try: - if self.impedance: - print("Starting with impedance testing") - self.ser_write(b'z') - else: - self.ser_write(b'b') - except Exception as e: - print("Something went wrong while asking the board to start streaming: " + str(e)) - self.streaming = True - self.packets_dropped = 0 - self.time_last_packet = timeit.default_timer() - - def find_port(self): - """Detects Ganglion board MAC address -- if more than 1 around, will select first. Needs root privilege.""" - - print("Try to detect Ganglion MAC address. NB: Turn on bluetooth and run as root for this to work! Might not work with every BLE dongles.") - scan_time = 5 - print("Scanning for 5 seconds nearby devices...") - - # From bluepy example - class ScanDelegate(DefaultDelegate): - def __init__(self): - DefaultDelegate.__init__(self) + """ + Handle a connection to an OpenBCI board. - def handleDiscovery(self, dev, isNewDev, isNewData): - if isNewDev: - print ("Discovered device: " + dev.addr) - elif isNewData: - print ("Received new data from: " + dev.addr) - - scanner = Scanner().withDelegate(ScanDelegate()) - devices = scanner.scan(scan_time) + Args: + port: MAC address of the Ganglion Board. "None" to attempt auto-detect. + aux: enable on not aux channels (i.e. switch to 18bit mode if set) + impedance: measures impedance when start streaming + timeout: in seconds, if set will try to disconnect / reconnect after a period without new data + -- should be high if impedance check + max_packets_to_skip: will try to disconnect / reconnect after too many packets are skipped + baud, filter_data, daisy: Not used, for compatibility with v3 + """ - nb_devices = len(devices) - if nb_devices < 1: - print("No BLE devices found. Check connectivity.") - return "" - else: - print("Found " + str(nb_devices) + ", detecting Ganglion") - list_mac = [] - list_id = [] - - for dev in devices: - # "Ganglion" should appear inside the "value" associated to "Complete Local Name", e.g. "Ganglion-b2a6" - for (adtype, desc, value) in dev.getScanData(): - if desc == "Complete Local Name" and value.startswith("Ganglion"): - list_mac.append(dev.addr) - list_id.append(value) - print("Got Ganglion: " + value + ", with MAC: " + dev.addr) - break - nb_ganglions = len(list_mac) - - if nb_ganglions < 1: - print("No Ganglion found ;(") - raise OSError('Cannot find OpenBCI Ganglion MAC address') - - if nb_ganglions > 1: - print("Found " + str(nb_ganglions) + ", selecting first") - - print("Selecting MAC address " + list_mac[0] + " for " + list_id[0]) - return list_mac[0] - - def ser_write(self, b): - """Access serial port object for write""" - self.char_write.write(b) - - def ser_read(self): - """Access serial port object for read""" - return self.char_read.read() - - def ser_inWaiting(self): - """ Slightly different from Cyton API, return True if ASCII messages are incoming.""" - # FIXME: might have a slight problem with thread because of notifications... - if self.delegate.receiving_ASCII: - # in case the packet indicating the end of the message drops, we use a 1s timeout - if timeit.default_timer() - self.delegate.time_last_ASCII > 2: - self.delegate.receiving_ASCII = False - return self.delegate.receiving_ASCII - - def getSampleRate(self): - return SAMPLE_RATE + def __init__(self, port=None, baud=0, filter_data=False, + scaled_output=True, daisy=False, log=True, aux=False, impedance=False, timeout=2, + max_packets_to_skip=20): + # unused, for compatibility with Cyton v3 API + self.daisy = False + # these one are used + self.log = log # print_incoming_text needs log + self.aux = aux + self.streaming = False + self.timeout = timeout + self.max_packets_to_skip = max_packets_to_skip + self.scaling_output = scaled_output + self.impedance = impedance + + # might be handy to know API + self.board_type = "ganglion" + + print("Looking for Ganglion board") + if port == None: + port = self.find_port() + self.port = port # find_port might not return string + + self.connect() + + self.streaming = False + # number of EEG channels and (optionally) accelerometer channel + self.eeg_channels_per_sample = 4 + self.aux_channels_per_sample = 3 + self.imp_channels_per_sample = 5 + self.read_state = 0 + self.log_packet_count = 0 + self.packets_dropped = 0 + self.time_last_packet = 0 + + # Disconnects from board when terminated + atexit.register(self.disconnect) + + def getBoardType(self): + """ Returns the version of the board """ + return self.board_type + + def setImpedance(self, flag): + """ Enable/disable impedance measure """ + self.impedance = bool(flag) + + def connect(self): + """ Connect to the board and configure it. Note: recreates various objects upon call. """ + print("Init BLE connection with MAC: " + self.port) + print("NB: if it fails, try with root privileges.") + self.gang = Peripheral(self.port, 'random') # ADDR_TYPE_RANDOM + + print("Get mainservice...") + self.service = self.gang.getServiceByUUID(BLE_SERVICE) + print("Got:" + str(self.service)) + + print("Get characteristics...") + self.char_read = self.service.getCharacteristics(BLE_CHAR_RECEIVE)[0] + print("receive, properties: " + str(self.char_read.propertiesToString()) + + ", supports read: " + str(self.char_read.supportsRead())) + + self.char_write = self.service.getCharacteristics(BLE_CHAR_SEND)[0] + print("write, properties: " + str(self.char_write.propertiesToString()) + + ", supports read: " + str(self.char_write.supportsRead())) + + self.char_discon = self.service.getCharacteristics(BLE_CHAR_DISCONNECT)[0] + print("disconnect, properties: " + str(self.char_discon.propertiesToString()) + + ", supports read: " + str(self.char_discon.supportsRead())) + + # set delegate to handle incoming data + self.delegate = GanglionDelegate(self.scaling_output) + self.gang.setDelegate(self.delegate) + + # enable AUX channel + if self.aux: + print("Enabling AUX data...") + try: + self.ser_write(b'n') + except Exception as e: + print("Something went wrong while enabling aux channels: " + str(e)) + + print("Turn on notifications") + # nead up-to-date bluepy, cf https://github.com/IanHarvey/bluepy/issues/53 + self.desc_notify = self.char_read.getDescriptors(forUUID=0x2902)[0] + try: + self.desc_notify.write(b"\x01") + except Exception as e: + print("Something went wrong while trying to enable notification: " + str(e)) + + print("Connection established") + + def init_streaming(self): + """ Tell the board to record like crazy. """ + try: + if self.impedance: + print("Starting with impedance testing") + self.ser_write(b'z') + else: + self.ser_write(b'b') + except Exception as e: + print("Something went wrong while asking the board to start streaming: " + str(e)) + self.streaming = True + self.packets_dropped = 0 + self.time_last_packet = timeit.default_timer() + + def find_port(self): + """Detects Ganglion board MAC address + If more than 1 around, will select first. Needs root privilege. + """ + + print("Try to detect Ganglion MAC address. " + "NB: Turn on bluetooth and run as root for this to work!" + "Might not work with every BLE dongles.") + scan_time = 5 + print("Scanning for 5 seconds nearby devices...") + + # From bluepy example + class ScanDelegate(DefaultDelegate): + def __init__(self): + DefaultDelegate.__init__(self) + + def handleDiscovery(self, dev, isNewDev, isNewData): + if isNewDev: + print("Discovered device: " + dev.addr) + elif isNewData: + print("Received new data from: " + dev.addr) + + scanner = Scanner().withDelegate(ScanDelegate()) + devices = scanner.scan(scan_time) + + nb_devices = len(devices) + if nb_devices < 1: + print("No BLE devices found. Check connectivity.") + return "" + else: + print("Found " + str(nb_devices) + ", detecting Ganglion") + list_mac = [] + list_id = [] + + for dev in devices: + # "Ganglion" should appear inside the "value" associated + # to "Complete Local Name", e.g. "Ganglion-b2a6" + for (adtype, desc, value) in dev.getScanData(): + if desc == "Complete Local Name" and value.startswith("Ganglion"): + list_mac.append(dev.addr) + list_id.append(value) + print("Got Ganglion: " + value + + ", with MAC: " + dev.addr) + break + nb_ganglions = len(list_mac) + + if nb_ganglions < 1: + print("No Ganglion found ;(") + raise OSError('Cannot find OpenBCI Ganglion MAC address') + + if nb_ganglions > 1: + print("Found " + str(nb_ganglions) + ", selecting first") + + print("Selecting MAC address " + list_mac[0] + " for " + list_id[0]) + return list_mac[0] + + def ser_write(self, b): + """Access serial port object for write""" + self.char_write.write(b) + + def ser_read(self): + """Access serial port object for read""" + return self.char_read.read() + + def ser_inWaiting(self): + """ Slightly different from Cyton API, return True if ASCII messages are incoming.""" + # FIXME: might have a slight problem with thread because of notifications... + if self.delegate.receiving_ASCII: + # in case the packet indicating the end of the message drops, we use a 1s timeout + if timeit.default_timer() - self.delegate.time_last_ASCII > 2: + self.delegate.receiving_ASCII = False + return self.delegate.receiving_ASCII + + def getSampleRate(self): + return SAMPLE_RATE + + def getNbEEGChannels(self): + """Will not get new data on impedance check.""" + return self.eeg_channels_per_sample + + def getNbAUXChannels(self): + """Might not be used depending on the mode.""" + return self.aux_channels_per_sample + + def getNbImpChannels(self): + """Might not be used depending on the mode.""" + return self.imp_channels_per_sample + + def start_streaming(self, callback, lapse=-1): + """ + Start handling streaming data from the board. Call a provided callback + for every single sample that is processed + + Args: + callback: A callback function or a list of functions that will receive a single argument + of the OpenBCISample object captured. + """ + if not self.streaming: + self.init_streaming() + + start_time = timeit.default_timer() + + # Enclose callback funtion in a list if it comes alone + if not isinstance(callback, list): + callback = [callback] + + while self.streaming: + # should the board get disconnected and we could not wait for notification + # anymore, a reco should be attempted through timeout mechanism + try: + # at most we will get one sample per packet + self.waitForNotifications(1. / self.getSampleRate()) + except Exception as e: + print("Something went wrong while waiting for a new sample: " + str(e)) + # retrieve current samples on the stack + samples = self.delegate.getSamples() + self.packets_dropped = self.delegate.getMaxPacketsDropped() + if samples: + self.time_last_packet = timeit.default_timer() + for call in callback: + for sample in samples: + call(sample) + + if (lapse > 0 and timeit.default_timer() - start_time > lapse): + self.stop() + if self.log: + self.log_packet_count = self.log_packet_count + 1 + + # Checking connection -- timeout and packets dropped + self.check_connection() + + def waitForNotifications(self, delay): + """ Allow some time for the board to receive new data. """ + self.gang.waitForNotifications(delay) + + def test_signal(self, signal): + """ Enable / disable test signal """ + if signal == 0: + self.warn("Disabling synthetic square wave") + try: + self.char_write.write(b']') + except Exception as e: + print("Something went wrong while setting signal: " + str(e)) + elif signal == 1: + self.warn("Eisabling synthetic square wave") + try: + self.char_write.write(b'[') + except Exception as e: + print("Something went wrong while setting signal: " + str(e)) + else: + self.warn( + "%s is not a known test signal. Valid signal is 0-1" % signal) + + def set_channel(self, channel, toggle_position): + """ Enable / disable channels """ + try: + # Commands to set toggle to on position + if toggle_position == 1: + if channel is 1: + self.ser.write(b'!') + if channel is 2: + self.ser.write(b'@') + if channel is 3: + self.ser.write(b'#') + if channel is 4: + self.ser.write(b'$') + # Commands to set toggle to off position + elif toggle_position == 0: + if channel is 1: + self.ser.write(b'1') + if channel is 2: + self.ser.write(b'2') + if channel is 3: + self.ser.write(b'3') + if channel is 4: + self.ser.write(b'4') + except Exception as e: + print("Something went wrong while setting channels: " + str(e)) + + """ - def getNbEEGChannels(self): - """Will not get new data on impedance check.""" - return self.eeg_channels_per_sample + Clean Up (atexit) - def getNbAUXChannels(self): - """Might not be used depending on the mode.""" - return self.aux_channels_per_sample - - def getNbImpChannels(self): - """Might not be used depending on the mode.""" - return self.imp_channels_per_sample - - def start_streaming(self, callback, lapse=-1): """ - Start handling streaming data from the board. Call a provided callback - for every single sample that is processed - Args: - callback: A callback function -- or a list of functions -- that will receive a single argument of the - OpenBCISample object captured. + def stop(self): + print("Stopping streaming...") + self.streaming = False + # connection might be already down here + try: + if self.impedance: + print("Stopping with impedance testing") + self.ser_write(b'Z') + else: + self.ser_write(b's') + except Exception as e: + print("Something went wrong while asking the board to stop streaming: " + str(e)) + if self.log: + logging.warning('sent : stopped streaming') + + def disconnect(self): + if (self.streaming == True): + self.stop() + print("Closing BLE..") + try: + self.char_discon.write(b' ') + except Exception as e: + print("Something went wrong while asking the board to disconnect: " + str(e)) + # should not try to read/write anything after that, will crash + try: + self.gang.disconnect() + except Exception as e: + print("Something went wrong while shutting down BLE link: " + str(e)) + logging.warning('BLE closed') + """ - if not self.streaming: - self.init_streaming() - - start_time = timeit.default_timer() - - # Enclose callback funtion in a list if it comes alone - if not isinstance(callback, list): - callback = [callback] - - while self.streaming: - # should the board get disconnected and we could not wait for notification anymore, a reco should be attempted through timeout mechanism - try: - # at most we will get one sample per packet - self.waitForNotifications(1./self.getSampleRate()) - except Exception as e: - print("Something went wrong while waiting for a new sample: " + str(e)) - # retrieve current samples on the stack - samples = self.delegate.getSamples() - self.packets_dropped = self.delegate.getMaxPacketsDropped() - if samples: - self.time_last_packet = timeit.default_timer() - for call in callback: - for sample in samples: - call(sample) - - if(lapse > 0 and timeit.default_timer() - start_time > lapse): - self.stop(); - if self.log: - self.log_packet_count = self.log_packet_count + 1; - # Checking connection -- timeout and packets dropped - self.check_connection() - - def waitForNotifications(self, delay): - """ Allow some time for the board to receive new data. """ - self.gang.waitForNotifications(delay) - - - def test_signal(self, signal): - """ Enable / disable test signal """ - if signal == 0: - self.warn("Disabling synthetic square wave") - try: - self.char_write.write(b']') - except Exception as e: - print("Something went wrong while setting signal: " + str(e)) - elif signal == 1: - self.warn("Eisabling synthetic square wave") - try: - self.char_write.write(b'[') - except Exception as e: - print("Something went wrong while setting signal: " + str(e)) - else: - self.warn("%s is not a known test signal. Valid signal is 0-1" %(signal)) - - def set_channel(self, channel, toggle_position): - """ Enable / disable channels """ - try: - #Commands to set toggle to on position - if toggle_position == 1: - if channel is 1: - self.ser.write(b'!') - if channel is 2: - self.ser.write(b'@') - if channel is 3: - self.ser.write(b'#') - if channel is 4: - self.ser.write(b'$') - #Commands to set toggle to off position - elif toggle_position == 0: - if channel is 1: - self.ser.write(b'1') - if channel is 2: - self.ser.write(b'2') - if channel is 3: - self.ser.write(b'3') - if channel is 4: - self.ser.write(b'4') - except Exception as e: - print("Something went wrong while setting channels: " + str(e)) - - """ - - Clean Up (atexit) - - """ - def stop(self): - print("Stopping streaming...") - self.streaming = False - # connection might be already down here - try: - if self.impedance: - print("Stopping with impedance testing") - self.ser_write(b'Z') - else: - self.ser_write(b's') - except Exception as e: - print("Something went wrong while asking the board to stop streaming: " + str(e)) - if self.log: - logging.warning('sent : stopped streaming') - - def disconnect(self): - if(self.streaming == True): - self.stop() - print("Closing BLE..") - try: - self.char_discon.write(b' ') - except Exception as e: - print("Something went wrong while asking the board to disconnect: " + str(e)) - # should not try to read/write anything after that, will crash - try: - self.gang.disconnect() - except Exception as e: - print("Something went wrong while shutting down BLE link: " + str(e)) - logging.warning('BLE closed') - - - """ - - SETTINGS AND HELPERS - - """ - def warn(self, text): - if self.log: - #log how many packets where sent succesfully in between warnings - if self.log_packet_count: - logging.info('Data packets received:'+str(self.log_packet_count)) - self.log_packet_count = 0; - logging.warning(text) - print("Warning: %s" % text) - - def check_connection(self): - """ Check connection quality in term of lag and number of packets drop. Reinit connection if necessary. FIXME: parameters given to the board will be lost.""" - # stop checking when we're no longer streaming - if not self.streaming: - return - #check number of dropped packets and duration without new packets, deco/reco if too large - if self.packets_dropped > self.max_packets_to_skip: - self.warn("Too many packets dropped, attempt to reconnect") - self.reconnect() - elif self.timeout > 0 and timeit.default_timer() - self.time_last_packet > self.timeout: - self.warn("Too long since got new data, attempt to reconnect") - #if error, attempt to reconect - self.reconnect() - - def reconnect(self): - """ In case of poor connection, will shut down and relaunch everything. FIXME: parameters given to the board will be lost.""" - self.warn('Reconnecting') - self.stop() - self.disconnect() - self.connect() - self.init_streaming() + SETTINGS AND HELPERS + + """ + + def warn(self, text): + if self.log: + # log how many packets where sent succesfully in between warnings + if self.log_packet_count: + logging.info('Data packets received:' + + str(self.log_packet_count)) + self.log_packet_count = 0 + logging.warning(text) + print("Warning: %s" % text) + + def check_connection(self): + """ Check connection quality in term of lag and number of packets drop. + Reinit connection if necessary. + FIXME: parameters given to the board will be lost. + """ + # stop checking when we're no longer streaming + if not self.streaming: + return + # check number of dropped packets and duration without new packets, deco/reco if too large + if self.packets_dropped > self.max_packets_to_skip: + self.warn("Too many packets dropped, attempt to reconnect") + self.reconnect() + elif self.timeout > 0 and timeit.default_timer() - self.time_last_packet > self.timeout: + self.warn("Too long since got new data, attempt to reconnect") + # if error, attempt to reconect + self.reconnect() + + def reconnect(self): + """ In case of poor connection, will shut down and relaunch everything. + FIXME: parameters given to the board will be lost.""" + self.warn('Reconnecting') + self.stop() + self.disconnect() + self.connect() + self.init_streaming() + class OpenBCISample(object): - """Object encapulsating a single sample from the OpenBCI board.""" - def __init__(self, packet_id, channel_data, aux_data, imp_data): - self.id = packet_id - self.channel_data = channel_data - self.aux_data = aux_data - self.imp_data = imp_data + """Object encapsulating a single sample from the OpenBCI board.""" + + def __init__(self, packet_id, channel_data, aux_data, imp_data): + self.id = packet_id + self.channel_data = channel_data + self.aux_data = aux_data + self.imp_data = imp_data + class GanglionDelegate(DefaultDelegate): - """ Called by bluepy (handling BLE connection) when new data arrive, parses samples. """ - def __init__(self, scaling_output = True): - DefaultDelegate.__init__(self) - # holds samples until OpenBCIBoard claims them - self.samples = [] - # detect gaps between packets - self.last_id = -1 - self.packets_dropped = 0 - # save uncompressed data to compute deltas - self.lastChannelData = [0, 0, 0, 0] - # 18bit data got here and then accelerometer with it - self.lastAcceleromoter = [0, 0, 0] - # when the board is manually set in the right mode (z to start, Z to stop), impedance will be measured. 4 channels + ref - self.lastImpedance = [0, 0, 0, 0, 0] - self.scaling_output = scaling_output - # handling incoming ASCII messages - self.receiving_ASCII = False - self.time_last_ASCII = timeit.default_timer() - - def handleNotification(self, cHandle, data): - if len(data) < 1: - print('Warning: a packet should at least hold one byte...') - return - self.parse(data) - - """ - PARSER: - Parses incoming data packet into OpenBCISample -- see docs. Will call the corresponding parse* function depending on the format of the packet. - """ - def parse(self, packet): - # bluepy returnds INT with python3 and STR with python2 - if type(packet) is str: - # convert a list of strings in bytes - unpac = struct.unpack(str(len(packet)) + 'B', "".join(packet)) - else: - unpac = packet - - start_byte = unpac[0] - - # Give the informative part of the packet to proper handler -- split between ID and data bytes - # Raw uncompressed - if start_byte == 0: - self.receiving_ASCII = False - self.parseRaw(start_byte, unpac[1:]) - # 18-bit compression with Accelerometer - elif start_byte >= 1 and start_byte <= 100: - self.receiving_ASCII = False - self.parse18bit(start_byte, unpac[1:]) - # 19-bit compression without Accelerometer - elif start_byte >=101 and start_byte <= 200: - self.receiving_ASCII = False - self.parse19bit(start_byte-100, unpac[1:]) - # Impedance Channel - elif start_byte >= 201 and start_byte <= 205: - self.receiving_ASCII = False - self.parseImpedance(start_byte, packet[1:]) - # Part of ASCII -- TODO: better formatting of incoming ASCII - elif start_byte == 206: - print("%\t" + str(packet[1:])) - self.receiving_ASCII = True - self.time_last_ASCII = timeit.default_timer() - - # End of ASCII message - elif start_byte == 207: - print("%\t" + str(packet[1:])) - print ("$$$") - self.receiving_ASCII = False - else: - print("Warning: unknown type of packet: " + str(start_byte)) - - def parseRaw(self, packet_id, packet): - """ Dealing with "Raw uncompressed" """ - if len(packet) != 19: - print('Wrong size, for raw data' + str(len(data)) + ' instead of 19 bytes') - return - - chan_data = [] - # 4 channels of 24bits, take values one by one - for i in range(0,12,3): - chan_data.append(conv24bitsToInt(packet[i:i+3])) - # save uncompressed raw channel for future use and append whole sample - self.pushSample(packet_id, chan_data, self.lastAcceleromoter, self.lastImpedance) - self.lastChannelData = chan_data - self.updatePacketsCount(packet_id) - - def parse19bit(self, packet_id, packet): - """ Dealing with "19-bit compression without Accelerometer" """ - if len(packet) != 19: - print('Wrong size, for 19-bit compression data' + str(len(data)) + ' instead of 19 bytes') - return - - # should get 2 by 4 arrays of uncompressed data - deltas = decompressDeltas19Bit(packet) - # the sample_id will be shifted - delta_id = 1 - for delta in deltas: - # convert from packet to sample id - sample_id = (packet_id - 1) * 2 + delta_id - # 19bit packets hold deltas between two samples - # TODO: use more broadly numpy - full_data = list(np.array(self.lastChannelData) - np.array(delta)) - # NB: aux data updated only in 18bit mode, send values here only to be consistent - self.pushSample(sample_id, full_data, self.lastAcceleromoter, self.lastImpedance) - self.lastChannelData = full_data - delta_id += 1 - self.updatePacketsCount(packet_id) - - - def parse18bit(self, packet_id, packet): - """ Dealing with "18-bit compression without Accelerometer" """ - if len(packet) != 19: - print('Wrong size, for 18-bit compression data' + str(len(data)) + ' instead of 19 bytes') - return - - # accelerometer X - if packet_id % 10 == 1: - self.lastAcceleromoter[0] = conv8bitToInt8(packet[18]) - # accelerometer Y - elif packet_id % 10 == 2: - self.lastAcceleromoter[1] = conv8bitToInt8(packet[18]) - # accelerometer Z - elif packet_id % 10 == 3: - self.lastAcceleromoter[2] = conv8bitToInt8(packet[18]) - - # deltas: should get 2 by 4 arrays of uncompressed data - deltas = decompressDeltas18Bit(packet[:-1]) - # the sample_id will be shifted - delta_id = 1 - for delta in deltas: - # convert from packet to sample id - sample_id = (packet_id - 1) * 2 + delta_id - # 19bit packets hold deltas between two samples - # TODO: use more broadly numpy - full_data = list(np.array(self.lastChannelData) - np.array(delta)) - self.pushSample(sample_id, full_data, self.lastAcceleromoter, self.lastImpedance) - self.lastChannelData = full_data - delta_id += 1 - self.updatePacketsCount(packet_id) - - - def parseImpedance(self, packet_id, packet): - """ Dealing with impedance data. packet: ASCII data. NB: will take few packet (seconds) to fill""" - if packet[-2:] != b"Z\n": - print('Wrong format for impedance check, should be ASCII ending with "Z\\n"') - - # convert from ASCII to actual value - imp_value = int(packet[:-2]) / 2 - # from 201 to 205 codes to the right array size - self.lastImpedance[packet_id- 201] = imp_value - self.pushSample(packet_id - 200, self.lastChannelData, self.lastAcceleromoter, self.lastImpedance) - - - def pushSample(self, sample_id, chan_data, aux_data, imp_data): - """ Add a sample to inner stack, setting ID and dealing with scaling if necessary. """ - if self.scaling_output: - chan_data = list(np.array(chan_data) * scale_fac_uVolts_per_count) - aux_data = list(np.array(aux_data) * scale_fac_accel_G_per_count) - sample = OpenBCISample(sample_id, chan_data, aux_data, imp_data) - self.samples.append(sample) - - def updatePacketsCount(self, packet_id): - """Update last packet ID and dropped packets""" - if self.last_id == -1: - self.last_id = packet_id - self.packets_dropped = 0 - return - # ID loops every 101 packets - if packet_id > self.last_id: - self.packets_dropped = packet_id - self.last_id - 1 - else: - self.packets_dropped = packet_id + 101 - self.last_id - 1 - self.last_id = packet_id - if self.packets_dropped > 0: - print("Warning: dropped " + str(self.packets_dropped) + " packets.") + """ Called by bluepy (handling BLE connection) when new data arrive, parses samples. """ - def getSamples(self): - """ Retrieve and remove from buffer last samples. """ - unstack_samples = self.samples - self.samples = [] - return unstack_samples + def __init__(self, scaling_output=True): + DefaultDelegate.__init__(self) + # holds samples until OpenBCIBoard claims them + self.samples = [] + # detect gaps between packets + self.last_id = -1 + self.packets_dropped = 0 + # save uncompressed data to compute deltas + self.lastChannelData = [0, 0, 0, 0] + # 18bit data got here and then accelerometer with it + self.lastAcceleromoter = [0, 0, 0] + # when the board is manually set in the right mode (z to start, Z to stop) + # impedance will be measured. 4 channels + ref + self.lastImpedance = [0, 0, 0, 0, 0] + self.scaling_output = scaling_output + # handling incoming ASCII messages + self.receiving_ASCII = False + self.time_last_ASCII = timeit.default_timer() + + def handleNotification(self, cHandle, data): + if len(data) < 1: + print('Warning: a packet should at least hold one byte...') + return + self.parse(data) - def getMaxPacketsDropped(self): - """ While processing last samples, how many packets were dropped?""" - # TODO: return max value of the last samples array? - return self.packets_dropped + """ + PARSER: + Parses incoming data packet into OpenBCISample -- see docs. + Will call the corresponding parse* function depending on the format of the packet. + """ + def parse(self, packet): + # bluepy returns INT with python3 and STR with python2 + if type(packet) is str: + # convert a list of strings in bytes + unpac = struct.unpack(str(len(packet)) + 'B', "".join(packet)) + else: + unpac = packet + + start_byte = unpac[0] + + # Give the informative part of the packet to proper handler + # split between ID and data bytes + # Raw uncompressed + if start_byte == 0: + self.receiving_ASCII = False + self.parseRaw(start_byte, unpac[1:]) + # 18-bit compression with Accelerometer + elif start_byte >= 1 and start_byte <= 100: + self.receiving_ASCII = False + self.parse18bit(start_byte, unpac[1:]) + # 19-bit compression without Accelerometer + elif start_byte >= 101 and start_byte <= 200: + self.receiving_ASCII = False + self.parse19bit(start_byte - 100, unpac[1:]) + # Impedance Channel + elif start_byte >= 201 and start_byte <= 205: + self.receiving_ASCII = False + self.parseImpedance(start_byte, packet[1:]) + # Part of ASCII -- TODO: better formatting of incoming ASCII + elif start_byte == 206: + print("%\t" + str(packet[1:])) + self.receiving_ASCII = True + self.time_last_ASCII = timeit.default_timer() + + # End of ASCII message + elif start_byte == 207: + print("%\t" + str(packet[1:])) + print("$$$") + self.receiving_ASCII = False + else: + print("Warning: unknown type of packet: " + str(start_byte)) + + def parseRaw(self, packet_id, packet): + """ Dealing with "Raw uncompressed" """ + if len(packet) != 19: + print('Wrong size, for raw data' + + str(len(packet)) + ' instead of 19 bytes') + return + + chan_data = [] + # 4 channels of 24bits, take values one by one + for i in range(0, 12, 3): + chan_data.append(conv24bitsToInt(packet[i:i + 3])) + # save uncompressed raw channel for future use and append whole sample + self.pushSample(packet_id, chan_data, + self.lastAcceleromoter, self.lastImpedance) + self.lastChannelData = chan_data + self.updatePacketsCount(packet_id) + + def parse19bit(self, packet_id, packet): + """ Dealing with "19-bit compression without Accelerometer" """ + if len(packet) != 19: + print('Wrong size, for 19-bit compression data' + + str(len(packet)) + ' instead of 19 bytes') + return + + # should get 2 by 4 arrays of uncompressed data + deltas = decompressDeltas19Bit(packet) + # the sample_id will be shifted + delta_id = 1 + for delta in deltas: + # convert from packet to sample id + sample_id = (packet_id - 1) * 2 + delta_id + # 19bit packets hold deltas between two samples + # TODO: use more broadly numpy + full_data = list(np.array(self.lastChannelData) - np.array(delta)) + # NB: aux data updated only in 18bit mode, send values here only to be consistent + self.pushSample(sample_id, full_data, + self.lastAcceleromoter, self.lastImpedance) + self.lastChannelData = full_data + delta_id += 1 + self.updatePacketsCount(packet_id) + + def parse18bit(self, packet_id, packet): + """ Dealing with "18-bit compression without Accelerometer" """ + if len(packet) != 19: + print('Wrong size, for 18-bit compression data' + + str(len(packet)) + ' instead of 19 bytes') + return + + # accelerometer X + if packet_id % 10 == 1: + self.lastAcceleromoter[0] = conv8bitToInt8(packet[18]) + # accelerometer Y + elif packet_id % 10 == 2: + self.lastAcceleromoter[1] = conv8bitToInt8(packet[18]) + # accelerometer Z + elif packet_id % 10 == 3: + self.lastAcceleromoter[2] = conv8bitToInt8(packet[18]) + + # deltas: should get 2 by 4 arrays of uncompressed data + deltas = decompressDeltas18Bit(packet[:-1]) + # the sample_id will be shifted + delta_id = 1 + for delta in deltas: + # convert from packet to sample id + sample_id = (packet_id - 1) * 2 + delta_id + # 19bit packets hold deltas between two samples + # TODO: use more broadly numpy + full_data = list(np.array(self.lastChannelData) - np.array(delta)) + self.pushSample(sample_id, full_data, + self.lastAcceleromoter, self.lastImpedance) + self.lastChannelData = full_data + delta_id += 1 + self.updatePacketsCount(packet_id) + + def parseImpedance(self, packet_id, packet): + """ Dealing with impedance data. packet: ASCII data. + NB: will take few packet (seconds) to fill + """ + if packet[-2:] != b"Z\n": + print('Wrong format for impedance check, should be ASCII ending with "Z\\n"') + + # convert from ASCII to actual value + imp_value = int(packet[:-2]) / 2 + # from 201 to 205 codes to the right array size + self.lastImpedance[packet_id - 201] = imp_value + self.pushSample(packet_id - 200, self.lastChannelData, + self.lastAcceleromoter, self.lastImpedance) + + def pushSample(self, sample_id, chan_data, aux_data, imp_data): + """ Add a sample to inner stack, setting ID and dealing with scaling if necessary. """ + if self.scaling_output: + chan_data = list(np.array(chan_data) * scale_fac_uVolts_per_count) + aux_data = list(np.array(aux_data) * scale_fac_accel_G_per_count) + sample = OpenBCISample(sample_id, chan_data, aux_data, imp_data) + self.samples.append(sample) + + def updatePacketsCount(self, packet_id): + """Update last packet ID and dropped packets""" + if self.last_id == -1: + self.last_id = packet_id + self.packets_dropped = 0 + return + # ID loops every 101 packets + if packet_id > self.last_id: + self.packets_dropped = packet_id - self.last_id - 1 + else: + self.packets_dropped = packet_id + 101 - self.last_id - 1 + self.last_id = packet_id + if self.packets_dropped > 0: + print("Warning: dropped " + str(self.packets_dropped) + " packets.") + + def getSamples(self): + """ Retrieve and remove from buffer last samples. """ + unstack_samples = self.samples + self.samples = [] + return unstack_samples + + def getMaxPacketsDropped(self): + """ While processing last samples, how many packets were dropped?""" + # TODO: return max value of the last samples array? + return self.packets_dropped """ DATA conversion, for the most part courtesy of OpenBCI_NodeJS_Ganglion """ - + + def conv24bitsToInt(unpacked): - """ Convert 24bit data coded on 3 bytes to a proper integer """ - if len(unpacked) != 3: - raise ValueError("Input should be 3 bytes long.") + """ Convert 24bit data coded on 3 bytes to a proper integer """ + if len(unpacked) != 3: + raise ValueError("Input should be 3 bytes long.") + + # FIXME: quick'n dirty, unpack wants strings later on + literal_read = struct.pack('3B', unpacked[0], unpacked[1], unpacked[2]) - # FIXME: quick'n dirty, unpack wants strings later on - literal_read = struct.pack('3B', unpacked[0], unpacked[1], unpacked[2]) + # 3byte int in 2s compliment + if (unpacked[0] > 127): + pre_fix = bytes(bytearray.fromhex('FF')) + else: + pre_fix = bytes(bytearray.fromhex('00')) - #3byte int in 2s compliment - if (unpacked[0] > 127): - pre_fix = bytes(bytearray.fromhex('FF')) - else: - pre_fix = bytes(bytearray.fromhex('00')) + literal_read = pre_fix + literal_read - literal_read = pre_fix + literal_read; + # unpack little endian(>) signed integer(i) (makes unpacking platform independent) + myInt = struct.unpack('>i', literal_read)[0] - #unpack little endian(>) signed integer(i) (makes unpacking platform independent) - myInt = struct.unpack('>i', literal_read)[0] + return myInt - return myInt def conv19bitToInt32(threeByteBuffer): - """ Convert 19bit data coded on 3 bytes to a proper integer (LSB bit 1 used as sign). """ - if len(threeByteBuffer) != 3: - raise ValueError("Input should be 3 bytes long.") + """ Convert 19bit data coded on 3 bytes to a proper integer (LSB bit 1 used as sign). """ + if len(threeByteBuffer) != 3: + raise ValueError("Input should be 3 bytes long.") - prefix = 0; + prefix = 0 + + # if LSB is 1, negative number, some hasty unsigned to signed conversion to do + if threeByteBuffer[2] & 0x01 > 0: + prefix = 0b1111111111111 + return ((prefix << 19) | (threeByteBuffer[0] << 16) | + (threeByteBuffer[1] << 8) | threeByteBuffer[2]) | ~0xFFFFFFFF + else: + return (prefix << 19) | (threeByteBuffer[0] << 16) |\ + (threeByteBuffer[1] << 8) | threeByteBuffer[2] - # if LSB is 1, negative number, some hasty unsigned to signed conversion to do - if threeByteBuffer[2] & 0x01 > 0: - prefix = 0b1111111111111; - return ((prefix << 19) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2]) | ~0xFFFFFFFF - else: - return (prefix << 19) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2] def conv18bitToInt32(threeByteBuffer): - """ Convert 18bit data coded on 3 bytes to a proper integer (LSB bit 1 used as sign) """ - if len(threeByteBuffer) != 3: - raise Valuerror("Input should be 3 bytes long.") - - prefix = 0; - - # if LSB is 1, negative number, some hasty unsigned to signed conversion to do - if threeByteBuffer[2] & 0x01 > 0: - prefix = 0b11111111111111; - return ((prefix << 18) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2]) | ~0xFFFFFFFF - else: - return (prefix << 18) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2] - + """ Convert 18bit data coded on 3 bytes to a proper integer (LSB bit 1 used as sign) """ + if len(threeByteBuffer) != 3: + raise ValueError("Input should be 3 bytes long.") + + prefix = 0 + + # if LSB is 1, negative number, some hasty unsigned to signed conversion to do + if threeByteBuffer[2] & 0x01 > 0: + prefix = 0b11111111111111 + return ((prefix << 18) | (threeByteBuffer[0] << 16) | + (threeByteBuffer[1] << 8) | threeByteBuffer[2]) | ~0xFFFFFFFF + else: + return (prefix << 18) | (threeByteBuffer[0] << 16) |\ + (threeByteBuffer[1] << 8) | threeByteBuffer[2] + + def conv8bitToInt8(byte): - """ Convert one byte to signed value """ + """ Convert one byte to signed value """ + + if byte > 127: + return (256 - byte) * (-1) + else: + return byte + - if byte > 127: - return (256-byte) * (-1) - else: - return byte - def decompressDeltas19Bit(buffer): - """ - Called to when a compressed packet is received. - buffer: Just the data portion of the sample. So 19 bytes. - return {Array} - An array of deltas of shape 2x4 (2 samples per packet and 4 channels per sample.) - """ - if len(buffer) != 19: - raise ValueError("Input should be 19 bytes long.") - - receivedDeltas = [[0, 0, 0, 0],[0, 0, 0, 0]] + """ + Called to when a compressed packet is received. + buffer: Just the data portion of the sample. So 19 bytes. + return {Array} - An array of deltas of shape 2x4 + (2 samples per packet and 4 channels per sample.) + """ + if len(buffer) != 19: + raise ValueError("Input should be 19 bytes long.") + + receivedDeltas = [[0, 0, 0, 0], [0, 0, 0, 0]] - # Sample 1 - Channel 1 - miniBuf = [ - (buffer[0] >> 5), - ((buffer[0] & 0x1F) << 3 & 0xFF) | (buffer[1] >> 5), - ((buffer[1] & 0x1F) << 3 & 0xFF) | (buffer[2] >> 5) + # Sample 1 - Channel 1 + miniBuf = [ + (buffer[0] >> 5), + ((buffer[0] & 0x1F) << 3 & 0xFF) | (buffer[1] >> 5), + ((buffer[1] & 0x1F) << 3 & 0xFF) | (buffer[2] >> 5) ] - receivedDeltas[0][0] = conv19bitToInt32(miniBuf) + receivedDeltas[0][0] = conv19bitToInt32(miniBuf) - # Sample 1 - Channel 2 - miniBuf = [ - (buffer[2] & 0x1F) >> 2, - (buffer[2] << 6 & 0xFF) | (buffer[3] >> 2), - (buffer[3] << 6 & 0xFF) | (buffer[4] >> 2) + # Sample 1 - Channel 2 + miniBuf = [ + (buffer[2] & 0x1F) >> 2, + (buffer[2] << 6 & 0xFF) | (buffer[3] >> 2), + (buffer[3] << 6 & 0xFF) | (buffer[4] >> 2) ] - receivedDeltas[0][1] = conv19bitToInt32(miniBuf) + receivedDeltas[0][1] = conv19bitToInt32(miniBuf) - # Sample 1 - Channel 3 - miniBuf = [ - ((buffer[4] & 0x03) << 1 & 0xFF) | (buffer[5] >> 7), - ((buffer[5] & 0x7F) << 1 & 0xFF) | (buffer[6] >> 7), - ((buffer[6] & 0x7F) << 1 & 0xFF) | (buffer[7] >> 7) + # Sample 1 - Channel 3 + miniBuf = [ + ((buffer[4] & 0x03) << 1 & 0xFF) | (buffer[5] >> 7), + ((buffer[5] & 0x7F) << 1 & 0xFF) | (buffer[6] >> 7), + ((buffer[6] & 0x7F) << 1 & 0xFF) | (buffer[7] >> 7) ] - receivedDeltas[0][2] = conv19bitToInt32(miniBuf) + receivedDeltas[0][2] = conv19bitToInt32(miniBuf) - # Sample 1 - Channel 4 - miniBuf = [ - ((buffer[7] & 0x7F) >> 4), - ((buffer[7] & 0x0F) << 4 & 0xFF) | (buffer[8] >> 4), - ((buffer[8] & 0x0F) << 4 & 0xFF) | (buffer[9] >> 4) + # Sample 1 - Channel 4 + miniBuf = [ + ((buffer[7] & 0x7F) >> 4), + ((buffer[7] & 0x0F) << 4 & 0xFF) | (buffer[8] >> 4), + ((buffer[8] & 0x0F) << 4 & 0xFF) | (buffer[9] >> 4) ] - receivedDeltas[0][3] = conv19bitToInt32(miniBuf) + receivedDeltas[0][3] = conv19bitToInt32(miniBuf) - # Sample 2 - Channel 1 - miniBuf = [ - ((buffer[9] & 0x0F) >> 1), - (buffer[9] << 7 & 0xFF) | (buffer[10] >> 1), - (buffer[10] << 7 & 0xFF) | (buffer[11] >> 1) + # Sample 2 - Channel 1 + miniBuf = [ + ((buffer[9] & 0x0F) >> 1), + (buffer[9] << 7 & 0xFF) | (buffer[10] >> 1), + (buffer[10] << 7 & 0xFF) | (buffer[11] >> 1) ] - receivedDeltas[1][0] = conv19bitToInt32(miniBuf) + receivedDeltas[1][0] = conv19bitToInt32(miniBuf) - # Sample 2 - Channel 2 - miniBuf = [ - ((buffer[11] & 0x01) << 2 & 0xFF) | (buffer[12] >> 6), - (buffer[12] << 2 & 0xFF) | (buffer[13] >> 6), - (buffer[13] << 2 & 0xFF) | (buffer[14] >> 6) + # Sample 2 - Channel 2 + miniBuf = [ + ((buffer[11] & 0x01) << 2 & 0xFF) | (buffer[12] >> 6), + (buffer[12] << 2 & 0xFF) | (buffer[13] >> 6), + (buffer[13] << 2 & 0xFF) | (buffer[14] >> 6) ] - receivedDeltas[1][1] = conv19bitToInt32(miniBuf) + receivedDeltas[1][1] = conv19bitToInt32(miniBuf) - # Sample 2 - Channel 3 - miniBuf = [ - ((buffer[14] & 0x38) >> 3), - ((buffer[14] & 0x07) << 5 & 0xFF) | ((buffer[15] & 0xF8) >> 3), - ((buffer[15] & 0x07) << 5 & 0xFF) | ((buffer[16] & 0xF8) >> 3) + # Sample 2 - Channel 3 + miniBuf = [ + ((buffer[14] & 0x38) >> 3), + ((buffer[14] & 0x07) << 5 & 0xFF) | ((buffer[15] & 0xF8) >> 3), + ((buffer[15] & 0x07) << 5 & 0xFF) | ((buffer[16] & 0xF8) >> 3) ] - receivedDeltas[1][2] = conv19bitToInt32(miniBuf) + receivedDeltas[1][2] = conv19bitToInt32(miniBuf) - # Sample 2 - Channel 4 - miniBuf = [(buffer[16] & 0x07), buffer[17], buffer[18]] - receivedDeltas[1][3] = conv19bitToInt32(miniBuf) + # Sample 2 - Channel 4 + miniBuf = [(buffer[16] & 0x07), buffer[17], buffer[18]] + receivedDeltas[1][3] = conv19bitToInt32(miniBuf) + + return receivedDeltas - return receivedDeltas; def decompressDeltas18Bit(buffer): - """ - Called to when a compressed packet is received. - buffer: Just the data portion of the sample. So 19 bytes. - return {Array} - An array of deltas of shape 2x4 (2 samples per packet and 4 channels per sample.) - """ - if len(buffer) != 18: - raise ValueError("Input should be 18 bytes long.") - - receivedDeltas = [[0, 0, 0, 0],[0, 0, 0, 0]] + """ + Called to when a compressed packet is received. + buffer: Just the data portion of the sample. So 19 bytes. + return {Array} - An array of deltas of shape 2x4 + (2 samples per packet and 4 channels per sample.) + """ + if len(buffer) != 18: + raise ValueError("Input should be 18 bytes long.") + + receivedDeltas = [[0, 0, 0, 0], [0, 0, 0, 0]] - # Sample 1 - Channel 1 - miniBuf = [ - (buffer[0] >> 6), - ((buffer[0] & 0x3F) << 2 & 0xFF) | (buffer[1] >> 6), - ((buffer[1] & 0x3F) << 2 & 0xFF) | (buffer[2] >> 6) + # Sample 1 - Channel 1 + miniBuf = [ + (buffer[0] >> 6), + ((buffer[0] & 0x3F) << 2 & 0xFF) | (buffer[1] >> 6), + ((buffer[1] & 0x3F) << 2 & 0xFF) | (buffer[2] >> 6) ] - receivedDeltas[0][0] = conv18bitToInt32(miniBuf); + receivedDeltas[0][0] = conv18bitToInt32(miniBuf) - # Sample 1 - Channel 2 - miniBuf = [ - (buffer[2] & 0x3F) >> 4, - (buffer[2] << 4 & 0xFF) | (buffer[3] >> 4), - (buffer[3] << 4 & 0xFF) | (buffer[4] >> 4) + # Sample 1 - Channel 2 + miniBuf = [ + (buffer[2] & 0x3F) >> 4, + (buffer[2] << 4 & 0xFF) | (buffer[3] >> 4), + (buffer[3] << 4 & 0xFF) | (buffer[4] >> 4) ] - receivedDeltas[0][1] = conv18bitToInt32(miniBuf); + receivedDeltas[0][1] = conv18bitToInt32(miniBuf) - # Sample 1 - Channel 3 - miniBuf = [ - (buffer[4] & 0x0F) >> 2, - (buffer[4] << 6 & 0xFF) | (buffer[5] >> 2), - (buffer[5] << 6 & 0xFF) | (buffer[6] >> 2) + # Sample 1 - Channel 3 + miniBuf = [ + (buffer[4] & 0x0F) >> 2, + (buffer[4] << 6 & 0xFF) | (buffer[5] >> 2), + (buffer[5] << 6 & 0xFF) | (buffer[6] >> 2) ] - receivedDeltas[0][2] = conv18bitToInt32(miniBuf); + receivedDeltas[0][2] = conv18bitToInt32(miniBuf) - # Sample 1 - Channel 4 - miniBuf = [ - (buffer[6] & 0x03), - buffer[7], - buffer[8] + # Sample 1 - Channel 4 + miniBuf = [ + (buffer[6] & 0x03), + buffer[7], + buffer[8] ] - receivedDeltas[0][3] = conv18bitToInt32(miniBuf); + receivedDeltas[0][3] = conv18bitToInt32(miniBuf) - # Sample 2 - Channel 1 - miniBuf = [ - (buffer[9] >> 6), - ((buffer[9] & 0x3F) << 2 & 0xFF) | (buffer[10] >> 6), - ((buffer[10] & 0x3F) << 2 & 0xFF) | (buffer[11] >> 6) + # Sample 2 - Channel 1 + miniBuf = [ + (buffer[9] >> 6), + ((buffer[9] & 0x3F) << 2 & 0xFF) | (buffer[10] >> 6), + ((buffer[10] & 0x3F) << 2 & 0xFF) | (buffer[11] >> 6) ] - receivedDeltas[1][0] = conv18bitToInt32(miniBuf); + receivedDeltas[1][0] = conv18bitToInt32(miniBuf) - # Sample 2 - Channel 2 - miniBuf = [ - (buffer[11] & 0x3F) >> 4, - (buffer[11] << 4 & 0xFF) | (buffer[12] >> 4), - (buffer[12] << 4 & 0xFF) | (buffer[13] >> 4) + # Sample 2 - Channel 2 + miniBuf = [ + (buffer[11] & 0x3F) >> 4, + (buffer[11] << 4 & 0xFF) | (buffer[12] >> 4), + (buffer[12] << 4 & 0xFF) | (buffer[13] >> 4) ] - receivedDeltas[1][1] = conv18bitToInt32(miniBuf); + receivedDeltas[1][1] = conv18bitToInt32(miniBuf) - # Sample 2 - Channel 3 - miniBuf = [ - (buffer[13] & 0x0F) >> 2, - (buffer[13] << 6 & 0xFF) | (buffer[14] >> 2), - (buffer[14] << 6 & 0xFF) | (buffer[15] >> 2) + # Sample 2 - Channel 3 + miniBuf = [ + (buffer[13] & 0x0F) >> 2, + (buffer[13] << 6 & 0xFF) | (buffer[14] >> 2), + (buffer[14] << 6 & 0xFF) | (buffer[15] >> 2) ] - receivedDeltas[1][2] = conv18bitToInt32(miniBuf); + receivedDeltas[1][2] = conv18bitToInt32(miniBuf) - # Sample 2 - Channel 4 - miniBuf = [ - (buffer[15] & 0x03), - buffer[16], - buffer[17] + # Sample 2 - Channel 4 + miniBuf = [ + (buffer[15] & 0x03), + buffer[16], + buffer[17] ] - receivedDeltas[1][3] = conv18bitToInt32(miniBuf); + receivedDeltas[1][3] = conv18bitToInt32(miniBuf) - return receivedDeltas; + return receivedDeltas diff --git a/openbci/plugins/__init__.py b/openbci/plugins/__init__.py index 60e9339..e3afa9d 100644 --- a/openbci/plugins/__init__.py +++ b/openbci/plugins/__init__.py @@ -1,4 +1,3 @@ - from .csv_collect import * from .noise_test import * from .streamer_lsl import * diff --git a/openbci/plugins/csv_collect.py b/openbci/plugins/csv_collect.py index d92423b..9829213 100755 --- a/openbci/plugins/csv_collect.py +++ b/openbci/plugins/csv_collect.py @@ -1,59 +1,62 @@ +from __future__ import print_function import csv import timeit import datetime import plugin_interface as plugintypes + class PluginCSVCollect(plugintypes.IPluginExtended): - def __init__(self, file_name="collect.csv", delim = ",", verbose=False): - now = datetime.datetime.now() - self.time_stamp = '%d-%d-%d_%d-%d-%d'%(now.year,now.month,now.day,now.hour,now.minute,now.second) - self.file_name = self.time_stamp - self.start_time = timeit.default_timer() - self.delim = delim - self.verbose = verbose - - def activate(self): - if len(self.args) > 0: - if 'no_time' in self.args: - self.file_name = self.args[0] - else: - self.file_name = self.args[0] + '_' + self.file_name; - if 'verbose' in self.args: - self.verbose = True - - self.file_name = self.file_name + '.csv' - print("Will export CSV to:" + self.file_name) - #Open in append mode - with open(self.file_name, 'a') as f: - f.write('%'+self.time_stamp + '\n') - - def deactivate(self): - print("Closing, CSV saved to:" + self.file_name) - return - - def show_help(self): - print("Optional argument: [filename] (default: collect.csv)") - - def __call__(self, sample): - t = timeit.default_timer() - self.start_time - - #print(timeSinceStart|Sample Id) - if self.verbose: - print("CSV: %f | %d" %(t,sample.id)) - - row = '' - row += str(t) - row += self.delim - row += str(sample.id) - row += self.delim - for i in sample.channel_data: - row += str(i) - row += self.delim - for i in sample.aux_data: - row += str(i) - row += self.delim - #remove last comma - row += '\n' - with open(self.file_name, 'a') as f: - f.write(row) \ No newline at end of file + def __init__(self, file_name="collect.csv", delim=",", verbose=False): + now = datetime.datetime.now() + self.time_stamp = '%d-%d-%d_%d-%d-%d' \ + % (now.year, now.month, now.day, now.hour, now.minute, now.second) + self.file_name = self.time_stamp + self.start_time = timeit.default_timer() + self.delim = delim + self.verbose = verbose + + def activate(self): + if len(self.args) > 0: + if 'no_time' in self.args: + self.file_name = self.args[0] + else: + self.file_name = self.args[0] + '_' + self.file_name + if 'verbose' in self.args: + self.verbose = True + + self.file_name = self.file_name + '.csv' + print("Will export CSV to:" + self.file_name) + # Open in append mode + with open(self.file_name, 'a') as f: + f.write('%' + self.time_stamp + '\n') + + def deactivate(self): + print("Closing, CSV saved to:" + self.file_name) + return + + def show_help(self): + print("Optional argument: [filename] (default: collect.csv)") + + def __call__(self, sample): + t = timeit.default_timer() - self.start_time + + # print(timeSinceStart|Sample Id) + if self.verbose: + print("CSV: %f | %d" % (t, sample.id)) + + row = '' + row += str(t) + row += self.delim + row += str(sample.id) + row += self.delim + for i in sample.channel_data: + row += str(i) + row += self.delim + for i in sample.aux_data: + row += str(i) + row += self.delim + # remove last comma + row += '\n' + with open(self.file_name, 'a') as f: + f.write(row) diff --git a/openbci/plugins/noise_test.py b/openbci/plugins/noise_test.py index cff2ac1..120dc57 100755 --- a/openbci/plugins/noise_test.py +++ b/openbci/plugins/noise_test.py @@ -1,40 +1,37 @@ +from __future__ import print_function import timeit import numpy as np import plugin_interface as plugintypes -class PluginNoiseTest(plugintypes.IPluginExtended): - # update counters value - def __call__(self, sample): - # keep tract of absolute value of - self.diff = np.add(self.diff,np.absolute(np.asarray(sample.channel_data))) - self.sample_count = self.sample_count + 1 - - elapsed_time = timeit.default_timer() - self.last_report - if elapsed_time > self.polling_interval: - channel_noise_power = np.divide(self.diff,self.sample_count) +class PluginNoiseTest(plugintypes.IPluginExtended): + # update counters value + def __call__(self, sample): + # keep tract of absolute value of + self.diff = np.add(self.diff, np.absolute(np.asarray(sample.channel_data))) + self.sample_count = self.sample_count + 1 - print (channel_noise_power) - self.diff = np.zeros(self.eeg_channels) - self.last_report = timeit.default_timer() + elapsed_time = timeit.default_timer() - self.last_report + if elapsed_time > self.polling_interval: + channel_noise_power = np.divide(self.diff, self.sample_count) + print(channel_noise_power) + self.diff = np.zeros(self.eeg_channels) + self.last_report = timeit.default_timer() - - # # Instanciate "monitor" thread - def activate(self): - # The difference between the ref and incoming signal. - # IMPORTANT: For noise tests, the reference and channel should have the same input signal. - self.diff = np.zeros(self.eeg_channels) - self.last_report = timeit.default_timer() - self.sample_count = 0 - self.polling_interval = 1.0 + # # Instanciate "monitor" thread + def activate(self): + # The difference between the ref and incoming signal. + # IMPORTANT: For noise tests, the reference and channel should have the same input signal. + self.diff = np.zeros(self.eeg_channels) + self.last_report = timeit.default_timer() + self.sample_count = 0 + self.polling_interval = 1.0 - if len(self.args) > 0: - self.polling_interval = float(self.args[0]) + if len(self.args) > 0: + self.polling_interval = float(self.args[0]) - - - def show_help(self): - print ("Optional argument: polling_interval -- in seconds, default: 10. \n \ - Returns the power of the system noise.\n \ - NOTE: The reference and channel should have the same input signal.") + def show_help(self): + print("Optional argument: polling_interval -- in seconds, default: 10. \n \ + Returns the power of the system noise.\n \ + NOTE: The reference and channel should have the same input signal.") diff --git a/openbci/plugins/print.py b/openbci/plugins/print.py index d44f5b2..32c5ce0 100755 --- a/openbci/plugins/print.py +++ b/openbci/plugins/print.py @@ -1,26 +1,30 @@ +from __future__ import print_function import plugin_interface as plugintypes + class PluginPrint(plugintypes.IPluginExtended): - def activate(self): - print("Print activated") - - # called with each new sample - def __call__(self, sample): - if sample: + def activate(self): + print("Print activated") + + # called with each new sample + def __call__(self, sample): + if sample: # print impedance if supported - if self.imp_channels > 0: - sample_string = "ID: %f\n%s\n%s\n%s" %(sample.id, str(sample.channel_data)[1:-1], str(sample.aux_data)[1:-1], str(sample.imp_data)[1:-1]) - else: - sample_string = "ID: %f\n%s\n%s" %(sample.id, str(sample.channel_data)[1:-1], str(sample.aux_data)[1:-1]) - print("---------------------------------") - print(sample_string) - print("---------------------------------") - - # DEBBUGING - # try: - # sample_string.decode('ascii') - # except UnicodeDecodeError: - # print("Not a ascii-encoded unicode string") - # else: - # print(sample_string) - + if self.imp_channels > 0: + sample_string = "ID: %f\n%s\n%s\n%s" %\ + (sample.id, str(sample.channel_data)[1:-1], + str(sample.aux_data)[1:-1], str(sample.imp_data)[1:-1]) + else: + sample_string = "ID: %f\n%s\n%s" % ( + sample.id, str(sample.channel_data)[1:-1], str(sample.aux_data)[1:-1]) + print("---------------------------------") + print(sample_string) + print("---------------------------------") + + # DEBBUGING + # try: + # sample_string.decode('ascii') + # except UnicodeDecodeError: + # print("Not a ascii-encoded unicode string") + # else: + # print(sample_string) diff --git a/openbci/plugins/sample_rate.py b/openbci/plugins/sample_rate.py index 900ea2f..d4e9dc3 100755 --- a/openbci/plugins/sample_rate.py +++ b/openbci/plugins/sample_rate.py @@ -1,3 +1,4 @@ +from __future__ import print_function import time import timeit from threading import Thread @@ -7,46 +8,48 @@ # counter for sampling rate nb_samples_out = -1 + # try to ease work for main loop class Monitor(Thread): - def __init__(self): - Thread.__init__(self) - self.nb_samples_out = -1 - - # Init time to compute sampling rate - self.tick = timeit.default_timer() - self.start_tick = self.tick - self.polling_interval = 10 - - def run(self): - while True: - # check FPS + listen for new connections - new_tick = timeit.default_timer() - elapsed_time = new_tick - self.tick - current_samples_out = nb_samples_out - print("--- at t: " + str(new_tick - self.start_tick) + " ---") - print("elapsed_time: " + str(elapsed_time)) - print("nb_samples_out: " + str(current_samples_out - self.nb_samples_out)) - sampling_rate = (current_samples_out - self.nb_samples_out) / elapsed_time - print("sampling rate: " + str(sampling_rate)) - self.tick = new_tick - self.nb_samples_out = nb_samples_out - time.sleep(self.polling_interval) + def __init__(self): + Thread.__init__(self) + self.nb_samples_out = -1 + + # Init time to compute sampling rate + self.tick = timeit.default_timer() + self.start_tick = self.tick + self.polling_interval = 10 + + def run(self): + while True: + # check FPS + listen for new connections + new_tick = timeit.default_timer() + elapsed_time = new_tick - self.tick + current_samples_out = nb_samples_out + print("--- at t: " + str(new_tick - self.start_tick) + " ---") + print("elapsed_time: " + str(elapsed_time)) + print("nb_samples_out: " + str(current_samples_out - self.nb_samples_out)) + sampling_rate = (current_samples_out - self.nb_samples_out) / elapsed_time + print("sampling rate: " + str(sampling_rate)) + self.tick = new_tick + self.nb_samples_out = nb_samples_out + time.sleep(self.polling_interval) + class PluginSampleRate(plugintypes.IPluginExtended): - # update counters value - def __call__(self, sample): - global nb_samples_out - nb_samples_out = nb_samples_out + 1 - - # Instanciate "monitor" thread - def activate(self): - monit = Monitor() - if len(self.args) > 0: - monit.polling_interval = float(self.args[0]) - # daemonize thread to terminate it altogether with the main when time will come - monit.daemon = True - monit.start() - - def show_help(self): - print("Optional argument: polling_interval -- in seconds, default: 10.") + # update counters value + def __call__(self, sample): + global nb_samples_out + nb_samples_out = nb_samples_out + 1 + + # Instanciate "monitor" thread + def activate(self): + monit = Monitor() + if len(self.args) > 0: + monit.polling_interval = float(self.args[0]) + # daemonize thread to terminate it altogether with the main when time will come + monit.daemon = True + monit.start() + + def show_help(self): + print("Optional argument: polling_interval -- in seconds, default: 10.") diff --git a/openbci/plugins/streamer_lsl.py b/openbci/plugins/streamer_lsl.py index 6400c1f..0c4e2dd 100755 --- a/openbci/plugins/streamer_lsl.py +++ b/openbci/plugins/streamer_lsl.py @@ -1,61 +1,75 @@ # download LSL and pylsl from https://code.google.com/p/labstreaminglayer/ # Eg: ftp://sccn.ucsd.edu/pub/software/LSL/SDK/liblsl-Python-1.10.2.zip # put in "lib" folder (same level as user.py) -import sys; sys.path.append('lib') # help python find pylsl relative to this example program - -from pylsl import StreamInfo, StreamOutlet +from __future__ import print_function import plugin_interface as plugintypes +from pylsl import StreamInfo, StreamOutlet +import sys -# Use LSL protocol to broadcast data using one stream for EEG, one stream for AUX, one last for impedance testing (on supported board, if enabled) +# help python find pylsl relative to this example program +sys.path.append('lib') + + +# Use LSL protocol to broadcast data using one stream for EEG, +# one stream for AUX, one last for impedance testing +# (on supported board, if enabled) class StreamerLSL(plugintypes.IPluginExtended): - # From IPlugin - def activate(self): - eeg_stream = "OpenBCI_EEG" - eeg_id = "openbci_eeg_id1" - aux_stream = "OpenBCI_AUX" - aux_id = "openbci_aux_id1" - imp_stream = "OpenBCI_Impedance" - imp_id = "openbci_imp_id1" - - if len(self.args) > 0: - eeg_stream = self.args[0] - if len(self.args) > 1: - eeg_id = self.args[1] - if len(self.args) > 2: - aux_stream = self.args[2] - if len(self.args) > 3: - aux_id = self.args[3] - if len(self.args) > 4: - imp_stream = self.args[4] - if len(self.args) > 5: - imp_id = self.args[5] - - # Create a new streams info, one for EEG values, one for AUX (eg, accelerometer) values - print("Creating LSL stream for EEG. Name:" + eeg_stream + "- ID:" + eeg_id + - "- data type: float32." + str(self.eeg_channels) + "channels at" + str(self.sample_rate) + "Hz.") - info_eeg = StreamInfo(eeg_stream, 'EEG', self.eeg_channels,self.sample_rate,'float32',eeg_id); - # NB: set float32 instead of int16 so as OpenViBE takes it into account - print("Creating LSL stream for AUX. Name:" + aux_stream + "- ID:" + aux_id + - "- data type: float32." + str(self.aux_channels) + "channels at" + str(self.sample_rate) + "Hz.") - info_aux = StreamInfo(aux_stream, 'AUX', self.aux_channels,self.sample_rate,'float32',aux_id); - - # make outlets - self.outlet_eeg = StreamOutlet(info_eeg) - self.outlet_aux = StreamOutlet(info_aux) - - if self.imp_channels > 0: - print("Creating LSL stream for Impedance. Name:" + imp_stream + "- ID:" + imp_id + - "- data type: float32." + str(self.imp_channels) + "channels at" + str(self.sample_rate) + "Hz.") - info_imp = StreamInfo(imp_stream, 'Impedance', self.imp_channels,self.sample_rate,'float32',imp_id); - self.outlet_imp = StreamOutlet(info_imp) - - # send channels values - def __call__(self, sample): - self.outlet_eeg.push_sample(sample.channel_data) - self.outlet_aux.push_sample(sample.aux_data) - if self.imp_channels > 0: - self.outlet_imp.push_sample(sample.imp_data) - - def show_help(self): - print("""Optional arguments: [EEG_stream_name [EEG_stream_ID [AUX_stream_name [AUX_stream_ID [Impedance_steam_name [Impedance_stream_ID]]]]]] - \t Defaults: "OpenBCI_EEG" / "openbci_eeg_id1" and "OpenBCI_AUX" / "openbci_aux_id1" / "OpenBCI_Impedance" / "openbci_imp_id1".""") + # From IPlugin + def activate(self): + eeg_stream = "OpenBCI_EEG" + eeg_id = "openbci_eeg_id1" + aux_stream = "OpenBCI_AUX" + aux_id = "openbci_aux_id1" + imp_stream = "OpenBCI_Impedance" + imp_id = "openbci_imp_id1" + + if len(self.args) > 0: + eeg_stream = self.args[0] + if len(self.args) > 1: + eeg_id = self.args[1] + if len(self.args) > 2: + aux_stream = self.args[2] + if len(self.args) > 3: + aux_id = self.args[3] + if len(self.args) > 4: + imp_stream = self.args[4] + if len(self.args) > 5: + imp_id = self.args[5] + + # Create a new streams info, one for EEG values, one for AUX (eg, accelerometer) values + print("Creating LSL stream for EEG. Name:" + eeg_stream + "- ID:" + eeg_id + + "- data type: float32." + str(self.eeg_channels) + + "channels at" + str(self.sample_rate) + "Hz.") + info_eeg = StreamInfo(eeg_stream, 'EEG', self.eeg_channels, + self.sample_rate, 'float32', eeg_id) + # NB: set float32 instead of int16 so as OpenViBE takes it into account + print("Creating LSL stream for AUX. Name:" + aux_stream + "- ID:" + aux_id + + "- data type: float32." + str(self.aux_channels) + + "channels at" + str(self.sample_rate) + "Hz.") + info_aux = StreamInfo(aux_stream, 'AUX', self.aux_channels, + self.sample_rate, 'float32', aux_id) + + # make outlets + self.outlet_eeg = StreamOutlet(info_eeg) + self.outlet_aux = StreamOutlet(info_aux) + + if self.imp_channels > 0: + print("Creating LSL stream for Impedance. Name:" + imp_stream + "- ID:" + + imp_id + "- data type: float32." + str(self.imp_channels) + + "channels at" + str(self.sample_rate) + "Hz.") + info_imp = StreamInfo(imp_stream, 'Impedance', self.imp_channels, + self.sample_rate, 'float32', imp_id) + self.outlet_imp = StreamOutlet(info_imp) + + # send channels values + def __call__(self, sample): + self.outlet_eeg.push_sample(sample.channel_data) + self.outlet_aux.push_sample(sample.aux_data) + if self.imp_channels > 0: + self.outlet_imp.push_sample(sample.imp_data) + + def show_help(self): + print("""Optional arguments: + [EEG_stream_name [EEG_stream_ID [AUX_stream_name [AUX_stream_ID [Impedance_steam_name [Impedance_stream_ID]]]]]] + \t Defaults: "OpenBCI_EEG" / "openbci_eeg_id1" and "OpenBCI_AUX" / "openbci_aux_id1" + / "OpenBCI_Impedance" / "openbci_imp_id1".""") diff --git a/openbci/plugins/streamer_osc.py b/openbci/plugins/streamer_osc.py index 05caea4..828cd57 100755 --- a/openbci/plugins/streamer_osc.py +++ b/openbci/plugins/streamer_osc.py @@ -1,54 +1,56 @@ - +from __future__ import print_function # requires python-osc from pythonosc import osc_message_builder from pythonosc import udp_client import plugin_interface as plugintypes -# Use OSC protocol to broadcast data (UDP layer), using "/openbci" stream. (NB. does not check numbers of channel as TCP server) + +# Use OSC protocol to broadcast data (UDP layer), using "/openbci" stream. +# (NB. does not check numbers of channel as TCP server) class StreamerOSC(plugintypes.IPluginExtended): - """ - - Relay OpenBCI values to OSC clients - - Args: - port: Port of the server - ip: IP address of the server - address: name of the stream - """ - - def __init__(self, ip='localhost', port=12345, address="/openbci"): - # connection infos - self.ip = ip - self.port = port - self.address = address - - # From IPlugin - def activate(self): - if len(self.args) > 0: - self.ip = self.args[0] - if len(self.args) > 1: - self.port = int(self.args[1]) - if len(self.args) > 2: - self.address = self.args[2] - # init network - print("Selecting OSC streaming. IP: " + self.ip + ", port: " + str(self.port) + ", address: " + self.address) - self.client = udp_client.SimpleUDPClient(self.ip, self.port) - - # From IPlugin: close connections, send message to client - def deactivate(self): - self.client.send_message("/quit") - - # send channels values - def __call__(self, sample): - # silently pass if connection drops - try: - self.client.send_message(self.address, sample.channel_data) - except: - return - - def show_help(self): - print("""Optional arguments: [ip [port [address]]] - \t ip: target IP address (default: 'localhost') - \t port: target port (default: 12345) - \t address: select target address (default: '/openbci')""") + """ + Relay OpenBCI values to OSC clients + + Args: + port: Port of the server + ip: IP address of the server + address: name of the stream + """ + + def __init__(self, ip='localhost', port=12345, address="/openbci"): + # connection infos + self.ip = ip + self.port = port + self.address = address + + # From IPlugin + def activate(self): + if len(self.args) > 0: + self.ip = self.args[0] + if len(self.args) > 1: + self.port = int(self.args[1]) + if len(self.args) > 2: + self.address = self.args[2] + # init network + print("Selecting OSC streaming. IP: " + self.ip + ", port: " + + str(self.port) + ", address: " + self.address) + self.client = udp_client.SimpleUDPClient(self.ip, self.port) + + # From IPlugin: close connections, send message to client + def deactivate(self): + self.client.send_message("/quit") + + # send channels values + def __call__(self, sample): + # silently pass if connection drops + try: + self.client.send_message(self.address, sample.channel_data) + except: + return + + def show_help(self): + print("""Optional arguments: [ip [port [address]]] + \t ip: target IP address (default: 'localhost') + \t port: target port (default: 12345) + \t address: select target address (default: '/openbci')""") diff --git a/openbci/plugins/streamer_tcp_server.py b/openbci/plugins/streamer_tcp_server.py index 0945f97..077504f 100755 --- a/openbci/plugins/streamer_tcp_server.py +++ b/openbci/plugins/streamer_tcp_server.py @@ -1,14 +1,23 @@ +from __future__ import print_function from threading import Thread -import socket, select, struct, time +import socket +import select +import struct +import time import plugin_interface as plugintypes -# Simple TCP server to "broadcast" data to clients, handling deconnections. Binary format use network endianness (i.e., big-endian), float32 + +# Simple TCP server to "broadcast" data to clients, handling deconnections. +# Binary format use network endianness (i.e., big-endian), float32 # TODO: does not listen for anything at the moment, could use it to set options # Handling new client in separate thread class MonitorStreamer(Thread): - """Launch and monitor a "Streamer" entity (incoming connections if implemented, current sampling rate).""" + """Launch and monitor a "Streamer" entity + (incoming connections if implemented, current sampling rate). + """ + # tcp_server: the TCPServer instance that will be used def __init__(self, streamer): Thread.__init__(self) @@ -37,11 +46,11 @@ class StreamerTCPServer(plugintypes.IPluginExtended): """ def __init__(self, ip='localhost', port=12345): - # list of socket clients - self.CONNECTION_LIST = [] - # connection infos - self.ip = ip - self.port = port + # list of socket clients + self.CONNECTION_LIST = [] + # connection infos + self.ip = ip + self.port = port # From IPlugin def activate(self): @@ -70,18 +79,19 @@ def initialize(self): self.server_socket.bind((self.ip, self.port)) self.server_socket.listen(1) print("Server started on port " + str(self.port)) - + # From Streamer, to be called each time we're willing to accept new connections def check_connections(self): - # First listen for new connections, and new connections only -- this is why we pass only server_socket - read_sockets,write_sockets,error_sockets = select.select([self.server_socket],[],[], 0) + # First listen for new connections, and new connections only, + # this is why we pass only server_socket + read_sockets, write_sockets, error_sockets = select.select([self.server_socket], [], [], 0) for sock in read_sockets: # New connection sockfd, addr = self.server_socket.accept() self.CONNECTION_LIST.append(sockfd) print("Client (%s, %s) connected" % addr) # and... don't bother with incoming messages - + # From IPlugin: close sockets, send message to client def deactivate(self): # close all remote connections @@ -91,15 +101,15 @@ def deactivate(self): sock.send("closing!\n") # at this point don't bother if message not sent except: - continue - sock.close(); + continue + sock.close() # close server socket - self.server_socket.close(); - + self.server_socket.close() + # broadcast channels values to all clients # as_string: many for debug, send values with a nice "[34.45, 30.4, -38.0]"-like format def __call__(self, sample, as_string=False): - values=sample.channel_data + values = sample.channel_data # save sockets that are closed to remove them later on outdated_list = [] for sock in self.CONNECTION_LIST: @@ -108,7 +118,7 @@ def __call__(self, sample, as_string=False): if as_string: sock.send(str(values) + "\n") else: - nb_channels=len(values) + nb_channels = len(values) # format for binary data, network endian (big) and float (float32) packer = struct.Struct('!%sf' % nb_channels) # convert values to bytes @@ -116,7 +126,8 @@ def __call__(self, sample, as_string=False): sock.send(packed_data) # TODO: should check if the correct number of bytes passed through except: - # sometimes (always?) it's only during the second write to a close socket that an error is raised? + # sometimes (always?) it's only during the second write to a close socket + # that an error is raised? print("Something bad happened, will close socket") outdated_list.append(sock) # now we are outside of the main list, it's time to remove outdated sockets, if any diff --git a/openbci/plugins/udp_server.py b/openbci/plugins/udp_server.py index 946e7cc..833bbc4 100755 --- a/openbci/plugins/udp_server.py +++ b/openbci/plugins/udp_server.py @@ -7,6 +7,7 @@ - websockets """ +from __future__ import print_function try: import cPickle as pickle except ImportError: @@ -16,26 +17,28 @@ import plugin_interface as plugintypes + # class PluginPrint(IPlugin): # # args: passed by command line # def activate(self, args): # print("Print activated") # # tell outside world that init went good # return True - + # def deactivate(self): # print("Print Deactivated") - + # def show_help(self): # print("I do not need any parameter, just printing stuff.") - + # # called with each new sample # def __call__(self, sample): -# sample_string = "ID: %f\n%s\n%s" %(sample.id, str(sample.channel_data)[1:-1], str(sample.aux_data)[1:-1]) +# sample_string = +# "ID: %f\n%s\n%s" %(sample.id, str(sample.channel_data)[1:-1], str(sample.aux_data)[1:-1]) # print("---------------------------------") # print(sample_string) # print("---------------------------------") - + # # DEBBUGING # # try: # # sample_string.decode('ascii') @@ -46,38 +49,38 @@ class UDPServer(plugintypes.IPluginExtended): - def __init__(self, ip='localhost', port=8888): - self.ip = ip - self.port = port - self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - def activate(self): - print("udp_server plugin") - print(self.args) - - if len(self.args) > 0: - self.ip = self.args[0] - if len(self.args) > 1: - self.port = int(self.args[1]) - - # init network - print("Selecting raw UDP streaming. IP: " + self.ip + ", port: " + str(self.port)) - - self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - print("Server started on port " + str(self.port)) - - def __call__(self, sample): - self.send_data(json.dumps(sample.channel_data)) - - def send_data(self, data): - self.server.sendto(data, (self.ip, self.port)) - - # From IPlugin: close sockets, send message to client - def deactivate(self): - self.server.close(); - - def show_help(self): - print("""Optional arguments: [ip [port]] + def __init__(self, ip='localhost', port=8888): + self.ip = ip + self.port = port + self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + def activate(self): + print("udp_server plugin") + print(self.args) + + if len(self.args) > 0: + self.ip = self.args[0] + if len(self.args) > 1: + self.port = int(self.args[1]) + + # init network + print("Selecting raw UDP streaming. IP: " + self.ip + ", port: " + str(self.port)) + + self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + print("Server started on port " + str(self.port)) + + def __call__(self, sample): + self.send_data(json.dumps(sample.channel_data)) + + def send_data(self, data): + self.server.sendto(data, (self.ip, self.port)) + + # From IPlugin: close sockets, send message to client + def deactivate(self): + self.server.close() + + def show_help(self): + print("""Optional arguments: [ip [port]] \t ip: target IP address (default: 'localhost') \t port: target port (default: 12345)""") diff --git a/openbci/utils/parse.py b/openbci/utils/parse.py index c450984..424df38 100644 --- a/openbci/utils/parse.py +++ b/openbci/utils/parse.py @@ -28,8 +28,8 @@ def __init__(self, def is_stop_byte(self, byte): """ - Used to check and see if a byte adheres to the stop byte structure of 0xCx where x is the set of numbers - from 0-F in hex of 0-15 in decimal. + Used to check and see if a byte adheres to the stop byte structure + of 0xCx where x is the set of numbers from 0-F in hex of 0-15 in decimal. :param byte: {int} - The number to test :return: {boolean} - True if `byte` follows the correct form """ @@ -64,16 +64,29 @@ def get_channel_data_array(self, raw_data_to_sample): # Channel data arrays are always 8 long for i in range(channels_in_packet): - counts = self.interpret_24_bit_as_int_32(raw_data_to_sample.raw_data_packet[(i * 3) + k.RAW_PACKET_POSITION_CHANNEL_DATA_START:(i * 3) + k.RAW_PACKET_POSITION_CHANNEL_DATA_START + 3]) - channel_data.append(raw_data_to_sample.scale_factors[i] * counts if raw_data_to_sample.scale else counts) + counts = self.interpret_24_bit_as_int_32( + raw_data_to_sample.raw_data_packet[ + (i * 3) + + k.RAW_PACKET_POSITION_CHANNEL_DATA_START:(i * 3) + + k.RAW_PACKET_POSITION_CHANNEL_DATA_START + 3 + ] + ) + channel_data.append( + raw_data_to_sample.scale_factors[i] * + counts if raw_data_to_sample.scale else counts + ) return channel_data def get_data_array_accel(self, raw_data_to_sample): accel_data = [] for i in range(k.RAW_PACKET_ACCEL_NUMBER_AXIS): - counts = self.interpret_16_bit_as_int_32(raw_data_to_sample.raw_data_packet[k.RAW_PACKET_POSITION_START_AUX + (i * 2): k.RAW_PACKET_POSITION_START_AUX + (i * 2) + 2]) - accel_data.append(k.CYTON_ACCEL_SCALE_FACTOR_GAIN * counts if raw_data_to_sample.scale else counts) + counts = self.interpret_16_bit_as_int_32( + raw_data_to_sample.raw_data_packet[ + k.RAW_PACKET_POSITION_START_AUX + + (i * 2): k.RAW_PACKET_POSITION_START_AUX + (i * 2) + 2]) + accel_data.append(k.CYTON_ACCEL_SCALE_FACTOR_GAIN * + counts if raw_data_to_sample.scale else counts) return accel_data def get_raw_packet_type(self, stop_byte): @@ -98,7 +111,6 @@ def interpret_24_bit_as_int_32(self, three_byte_buffer): return struct.unpack('>i', three_byte_buffer)[0] def parse_packet_standard_accel(self, raw_data_to_sample): - """ :param raw_data_to_sample: RawDataToSample @@ -124,9 +136,15 @@ def parse_packet_standard_accel(self, raw_data_to_sample): sample_object.channel_data = self.get_channel_data_array(raw_data_to_sample) - sample_object.sample_number = raw_data_to_sample.raw_data_packet[k.RAW_PACKET_POSITION_SAMPLE_NUMBER] - sample_object.start_byte = raw_data_to_sample.raw_data_packet[k.RAW_PACKET_POSITION_START_BYTE] - sample_object.stop_byte = raw_data_to_sample.raw_data_packet[k.RAW_PACKET_POSITION_STOP_BYTE] + sample_object.sample_number = raw_data_to_sample.raw_data_packet[ + k.RAW_PACKET_POSITION_SAMPLE_NUMBER + ] + sample_object.start_byte = raw_data_to_sample.raw_data_packet[ + k.RAW_PACKET_POSITION_START_BYTE + ] + sample_object.stop_byte = raw_data_to_sample.raw_data_packet[ + k.RAW_PACKET_POSITION_STOP_BYTE + ] sample_object.valid = True @@ -162,9 +180,11 @@ def transform_raw_data_packet_to_sample(self, raw_data): sample = self.parse_packet_standard_accel(self.raw_data_to_sample) elif packet_type == k.RAW_PACKET_TYPE_STANDARD_RAW_AUX: sample = self.parse_packet_standard_raw_aux(self.raw_data_to_sample) - elif packet_type == k.RAW_PACKET_TYPE_ACCEL_TIME_SYNC_SET or packet_type == k.RAW_PACKET_TYPE_ACCEL_TIME_SYNCED: + elif packet_type == k.RAW_PACKET_TYPE_ACCEL_TIME_SYNC_SET or \ + packet_type == k.RAW_PACKET_TYPE_ACCEL_TIME_SYNCED: sample = self.parse_packet_time_synced_accel(self.raw_data_to_sample) - elif packet_type == k.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNC_SET or packet_type == k.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNCED: + elif packet_type == k.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNC_SET or \ + packet_type == k.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNCED: sample = self.parse_packet_time_synced_raw_aux(self.raw_data_to_sample) else: sample = OpenBCISample() @@ -185,25 +205,33 @@ def transform_raw_data_packet_to_sample(self, raw_data): def make_daisy_sample_object_wifi(self, lower_sample_object, upper_sample_object): """ /** - * @description Used to make one sample object from two sample objects. The sample number of the new daisy sample will - * be the upperSampleObject's sample number divded by 2. This allows us to preserve consecutive sample numbers that - * flip over at 127 instead of 255 for an 8 channel. The daisySampleObject will also have one `channelData` array - * with 16 elements inside it, with the lowerSampleObject in the lower indices and the upperSampleObject in the - * upper set of indices. The auxData from both channels shall be captured in an object called `auxData` which - * contains two arrays referenced by keys `lower` and `upper` for the `lowerSampleObject` and `upperSampleObject`, - * respectively. The timestamps shall be averaged and moved into an object called `timestamp`. Further, the - * un-averaged timestamps from the `lowerSampleObject` and `upperSampleObject` shall be placed into an object called - * `_timestamps` which shall contain two keys `lower` and `upper` which contain the original timestamps for their - * respective sampleObjects. - * @param lowerSampleObject {Object} - Lower 8 channels with odd sample number - * @param upperSampleObject {Object} - Upper 8 channels with even sample number - * @returns {Object} - The new merged daisy sample object - */ + * @description Used to make one sample object from two sample + * objects. The sample number of the new daisy sample will be the + * upperSampleObject's sample number divded by 2. This allows us + * to preserve consecutive sample numbers that flip over at 127 + * instead of 255 for an 8 channel. The daisySampleObject will + * also have one `channelData` array with 16 elements inside it, + * with the lowerSampleObject in the lower indices and the + * upperSampleObject in the upper set of indices. The auxData from + * both channels shall be captured in an object called `auxData` + * which contains two arrays referenced by keys `lower` and + * `upper` for the `lowerSampleObject` and `upperSampleObject`, + * respectively. The timestamps shall be averaged and moved into + * an object called `timestamp`. Further, the un-averaged + * timestamps from the `lowerSampleObject` and `upperSampleObject` + * shall be placed into an object called `_timestamps` which shall + * contain two keys `lower` and `upper` which contain the original + * timestamps for their respective sampleObjects. + * @param lowerSampleObject {Object} - Lower 8 channels with odd sample number + * @param upperSampleObject {Object} - Upper 8 channels with even sample number + * @returns {Object} - The new merged daisy sample object + */ """ daisy_sample_object = OpenBCISample() if lower_sample_object.channel_data is not None: - daisy_sample_object.channel_data = lower_sample_object.channel_data + upper_sample_object.channel_data + daisy_sample_object.channel_data = lower_sample_object.channel_data + \ + upper_sample_object.channel_data daisy_sample_object.sample_number = upper_sample_object.sample_number daisy_sample_object.id = daisy_sample_object.sample_number @@ -224,7 +252,8 @@ def make_daisy_sample_object_wifi(self, lower_sample_object, upper_sample_object } if lower_sample_object.accel_data: - if lower_sample_object.accel_data[0] > 0 or lower_sample_object.accel_data[1] > 0 or lower_sample_object.accel_data[2] > 0: + if lower_sample_object.accel_data[0] > 0 or lower_sample_object.accel_data[1] > 0 or \ + lower_sample_object.accel_data[2] > 0: daisy_sample_object.accel_data = lower_sample_object.accel_data else: daisy_sample_object.accel_data = upper_sample_object.accel_data @@ -255,6 +284,7 @@ def make_daisy_sample_object_wifi(self, lower_sample_object, upper_sample_object return samples; } """ + def transform_raw_data_packets_to_sample(self, raw_data_packets): samples = [] @@ -268,6 +298,7 @@ def transform_raw_data_packets_to_sample(self, raw_data_packets): class RawDataToSample(object): """Object encapulsating a parsing object.""" + def __init__(self, accel_data=None, gains=None, @@ -310,6 +341,7 @@ def __init__(self, class OpenBCISample(object): """Object encapulsating a single sample from the OpenBCI board.""" + def __init__(self, aux_data=None, board_time=0, diff --git a/openbci/utils/ssdp.py b/openbci/utils/ssdp.py index 99d1e36..5446024 100755 --- a/openbci/utils/ssdp.py +++ b/openbci/utils/ssdp.py @@ -14,6 +14,7 @@ import socket import sys + pyVersion = sys.version_info[0] if pyVersion == 2: # Imports for Python 2 @@ -31,12 +32,12 @@ def makefile(self, *args, **kw): return self def __init__(self, response): - + if pyVersion == 2: r = httplib.HTTPResponse(self._FakeSocket(response)) else: r = http.client.HTTPResponse(self._FakeSocket(response)) - + r.begin() self.location = r.getheader("location") self.usn = r.getheader("usn") @@ -53,8 +54,8 @@ def discover(service, timeout=5, retries=1, mx=3, wifi_found_cb=None): 'M-SEARCH * HTTP/1.1', 'HOST: {0}:{1}', 'MAN: "ssdp:discover"', - 'ST: {st}','MX: {mx}','','']) - + 'ST: {st}', 'MX: {mx}', '', '']) + socket.setdefaulttimeout(timeout) responses = {} for _ in range(retries): @@ -63,13 +64,13 @@ def discover(service, timeout=5, retries=1, mx=3, wifi_found_cb=None): sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) sockMessage = message.format(*group, st=service, mx=mx) if pyVersion == 3: - sockMessage = sockMessage.encode("utf-8") + sockMessage = sockMessage.encode("utf-8") sock.sendto(sockMessage, group) while True: try: response = SSDPResponse(sock.recv(1024)) if wifi_found_cb is not None: - wifi_found_cb(response) + wifi_found_cb(response) responses[response.location] = response except socket.timeout: break diff --git a/openbci/utils/utilities.py b/openbci/utils/utilities.py index e90142d..423be01 100644 --- a/openbci/utils/utilities.py +++ b/openbci/utils/utilities.py @@ -1,4 +1,4 @@ -from openbci.utils.constants import Constants as k +from openbci.utils.constants import Constants def make_tail_byte_from_packet_type(packet_type): @@ -11,13 +11,13 @@ def make_tail_byte_from_packet_type(packet_type): if packet_type < 0 or packet_type > 15: packet_type = 0 - return k.RAW_BYTE_STOP | packet_type + return Constants.RAW_BYTE_STOP | packet_type def sample_number_normalize(sample_number=None): if sample_number is not None: - if sample_number > k.SAMPLE_NUMBER_MAX_CYTON: - sample_number = k.SAMPLE_NUMBER_MAX_CYTON + if sample_number > Constants.SAMPLE_NUMBER_MAX_CYTON: + sample_number = Constants.SAMPLE_NUMBER_MAX_CYTON else: sample_number = 0x45 @@ -25,40 +25,79 @@ def sample_number_normalize(sample_number=None): def sample_packet(sample_number=0x45): - return bytearray([0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 0, 1, 0, 2, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_STANDARD_ACCEL)]) + return bytearray( + [0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, + 0, 0, 6, 0, 0, 7, 0, + 0, 8, 0, 0, 0, 1, 0, 2, + make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_STANDARD_ACCEL)]) def sample_packet_zero(sample_number): - return bytearray([0xA0, sample_number_normalize(sample_number), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_STANDARD_ACCEL)]) + return bytearray( + [0xA0, sample_number_normalize(sample_number), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_STANDARD_ACCEL)]) def sample_packet_real(sample_number): - return bytearray([0xA0, sample_number_normalize(sample_number), 0x8F, 0xF2, 0x40, 0x8F, 0xDF, 0xF4, 0x90, 0x2B, 0xB6, 0x8F, 0xBF, 0xBF, 0x7F, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x94, 0x25, 0x34, 0x20, 0xB6, 0x7D, 0, 0xE0, 0, 0xE0, 0x0F, 0x70, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_STANDARD_ACCEL)]) + return bytearray( + [0xA0, sample_number_normalize(sample_number), 0x8F, 0xF2, 0x40, 0x8F, 0xDF, 0xF4, 0x90, + 0x2B, 0xB6, 0x8F, 0xBF, + 0xBF, 0x7F, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x94, 0x25, 0x34, 0x20, 0xB6, 0x7D, 0, 0xE0, 0, + 0xE0, 0x0F, 0x70, + make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_STANDARD_ACCEL)]) def sample_packet_standard_raw_aux(sample_number): - return bytearray([0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 1, 2, 3, 4, 5, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_STANDARD_RAW_AUX)]) + return bytearray( + [0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, + 0, 0, 6, 0, 0, 7, 0, + 0, 8, 0, 1, 2, 3, 4, 5, + make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_STANDARD_RAW_AUX)]) def sample_packet_accel_time_sync_set(sample_number): - return bytearray([0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 1, 0, 0, 0, 1, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_ACCEL_TIME_SYNC_SET)]) + return bytearray( + [0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, + 0, 0, 6, 0, 0, 7, 0, + 0, 8, 0, 1, 0, 0, 0, 1, + make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_ACCEL_TIME_SYNC_SET)]) def sample_packet_accel_time_synced(sample_number): - return bytearray([0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 1, 0, 0, 0, 1, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_ACCEL_TIME_SYNCED)]) + return bytearray( + [0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, + 0, 0, 6, 0, 0, 7, 0, + 0, 8, 0, 1, 0, 0, 0, 1, + make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_ACCEL_TIME_SYNCED)]) def sample_packet_raw_aux_time_sync_set(sample_number): - return bytearray([0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0x00, 0x01, 0, 0, 0, 1, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNC_SET)]) + return bytearray( + [0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, + 0, 0, 6, 0, 0, 7, 0, + 0, 8, 0x00, 0x01, 0, 0, 0, 1, + make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNC_SET)]) def sample_packet_raw_aux_time_synced(sample_number): - return bytearray([0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0, 6, 0, 0, 7, 0, 0, 8, 0x00, 0x01, 0, 0, 0, 1, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNCED)]) + return bytearray( + [0xA0, sample_number_normalize(sample_number), 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, + 0, 0, 6, 0, 0, 7, 0, + 0, 8, 0x00, 0x01, 0, 0, 0, 1, + make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNCED)]) def sample_packet_impedance(channel_number): - return bytearray([0xA0, channel_number, 54, 52, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, make_tail_byte_from_packet_type(k.RAW_PACKET_TYPE_IMPEDANCE)]) + return bytearray( + [0xA0, channel_number, 54, 52, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + 0, make_tail_byte_from_packet_type(Constants.RAW_PACKET_TYPE_IMPEDANCE)]) def sample_packet_user_defined(): - return bytearray([0xA0, 0x00, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, make_tail_byte_from_packet_type(k.OBCIStreamPacketUserDefinedType)]); + return bytearray( + [0xA0, 0x00, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + make_tail_byte_from_packet_type(Constants.OBCIStreamPacketUserDefinedType)]) diff --git a/openbci/wifi.py b/openbci/wifi.py index 790a914..5f2c7a4 100755 --- a/openbci/wifi.py +++ b/openbci/wifi.py @@ -16,6 +16,7 @@ def handle_sample(sample): TODO: Cyton Raw """ +from __future__ import print_function import asyncore import atexit import json @@ -23,6 +24,7 @@ def handle_sample(sample): import re import socket import timeit + try: import urllib2 except ImportError: @@ -31,7 +33,7 @@ def handle_sample(sample): import requests import xmltodict -from openbci.utils import k, ParseRaw, OpenBCISample, ssdp +from openbci.utils import Constants, ParseRaw, OpenBCISample, ssdp SAMPLE_RATE = 0 # Hz @@ -49,12 +51,15 @@ class OpenBCIWiFi(object): Args: ip_address: The IP address of the WiFi Shield, "None" to attempt auto-detect. - shield_name: The unique name of the WiFi Shield, such as `OpenBCI-2AD4`, will use SSDP to get IP address still, - if `shield_name` is "None" and `ip_address` is "None", will connect to the first WiFi Shield found using SSDP - sample_rate: The sample rate to set the attached board to. If the sample rate picked is not a sample rate the attached - board can support, i.e. you send 300 to Cyton, then error will be thrown. + shield_name: The unique name of the WiFi Shield, such as `OpenBCI-2AD4`, will use SSDP to + get IP address still, if `shield_name` is "None" and `ip_address` is "None", + will connect to the first WiFi Shield found using SSDP + sample_rate: The sample rate to set the attached board to. If the sample rate picked + is not a sample rate the attached board can support, i.e. you send 300 to Cyton, + then error will be thrown. log: - timeout: in seconds, disconnect / reconnect after a period without new data -- should be high if impedance check + timeout: in seconds, disconnect / reconnect after a period without new data + should be high if impedance check max_packets_to_skip: will try to disconnect / reconnect after too many packets are skipped """ @@ -97,7 +102,8 @@ def __init__(self, ip_address=None, shield_name=None, sample_rate=None, log=True self.local_wifi_server = WiFiShieldServer(self.local_ip_address, 0) self.local_wifi_server_port = self.local_wifi_server.socket.getsockname()[1] if self.log: - print("Opened socket on %s:%d" % (self.local_ip_address, self.local_wifi_server_port)) + print("Opened socket on %s:%d" % + (self.local_ip_address, self.local_wifi_server_port)) if ip_address is None: for i in range(ssdp_attempts): @@ -157,57 +163,66 @@ def connect(self): if res_board.status_code == 200: board_info = res_board.json() if not board_info['board_connected']: - raise RuntimeError("No board connected to WiFi Shield. To learn how to connect to a Cyton or Ganglion visit http://docs.openbci.com/Tutorials/03-Wifi_Getting_Started_Guide") + raise RuntimeError("No board connected to WiFi Shield. " + "To learn how to connect to a Cyton or Ganglion visit " + "http://docs.openbci.com/Tutorials/03-Wifi_Getting_Started_Guide") self.board_type = board_info['board_type'] self.eeg_channels_per_sample = board_info['num_channels'] if self.log: - print("Connected to %s with %s channels" % (self.board_type, self.eeg_channels_per_sample)) + print("Connected to %s with %s channels" % + (self.board_type, self.eeg_channels_per_sample)) self.gains = None - if self.board_type == k.BOARD_CYTON: + if self.board_type == Constants.BOARD_CYTON: self.gains = [24, 24, 24, 24, 24, 24, 24, 24] self.daisy = False - elif self.board_type == k.BOARD_DAISY: - self.gains = [24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24] + elif self.board_type == Constants.BOARD_DAISY: + self.gains = [24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24] self.daisy = True - elif self.board_type == k.BOARD_GANGLION: + elif self.board_type == Constants.BOARD_GANGLION: self.gains = [51, 51, 51, 51] self.daisy = False self.local_wifi_server.set_daisy(daisy=self.daisy) - self.local_wifi_server.set_parser(ParseRaw(gains=self.gains, board_type=self.board_type)) + self.local_wifi_server.set_parser( + ParseRaw(gains=self.gains, board_type=self.board_type)) if self.high_speed: output_style = 'raw' else: output_style = 'json' res_tcp_post = requests.post("http://%s/tcp" % self.ip_address, - json={ - 'ip': self.local_ip_address, - 'port': self.local_wifi_server_port, - 'output': output_style, - 'delimiter': True, - 'latency': self.latency - }) + json={ + 'ip': self.local_ip_address, + 'port': self.local_wifi_server_port, + 'output': output_style, + 'delimiter': True, + 'latency': self.latency + }) if res_tcp_post.status_code == 200: tcp_status = res_tcp_post.json() if tcp_status['connected']: if self.log: print("WiFi Shield to Python TCP Socket Established") else: - raise RuntimeWarning("WiFi Shield is not able to connect to local server. Please open an issue.") + raise RuntimeWarning("WiFi Shield is not able to connect to local server." + "Please open an issue.") def init_streaming(self): """ Tell the board to record like crazy. """ - res_stream_start = requests.get("http://%s/stream/start" % self.ip_address) + res_stream_start = requests.get( + "http://%s/stream/start" % self.ip_address) if res_stream_start.status_code == 200: self.streaming = True self.packets_dropped = 0 self.time_last_packet = timeit.default_timer() else: - raise EnvironmentError("Unable to start streaming. Check API for status code %d on /stream/start" % res_stream_start.status_code) + raise EnvironmentError("Unable to start streaming." + "Check API for status code %d on /stream/start" + % res_stream_start.status_code) def find_wifi_shield(self, shield_name=None, wifi_shield_cb=None): - """Detects Ganglion board MAC address -- if more than 1 around, will select first. Needs root privilege.""" + """Detects Ganglion board MAC address if more than 1, will select first. Needs root.""" if self.log: print("Try to find WiFi shields on your local wireless network") @@ -220,14 +235,16 @@ def find_wifi_shield(self, shield_name=None, wifi_shield_cb=None): def wifi_shield_found(response): res = requests.get(response.location, verify=False).text device_description = xmltodict.parse(res) - cur_shield_name = str(device_description['root']['device']['serialNumber']) + cur_shield_name = str( + device_description['root']['device']['serialNumber']) cur_base_url = str(device_description['root']['URLBase']) cur_ip_address = re.findall(r'[0-9]+(?:\.[0-9]+){3}', cur_base_url)[0] list_id.append(cur_shield_name) list_ip.append(cur_ip_address) found_shield = True if shield_name is None: - print("Found WiFi Shield %s with IP Address %s" % (cur_shield_name, cur_ip_address)) + print("Found WiFi Shield %s with IP Address %s" % + (cur_shield_name, cur_ip_address)) if wifi_shield_cb is not None: wifi_shield_cb(cur_ip_address) else: @@ -235,7 +252,8 @@ def wifi_shield_found(response): if wifi_shield_cb is not None: wifi_shield_cb(cur_ip_address) - ssdp_hits = ssdp.discover("urn:schemas-upnp-org:device:Basic:1", timeout=self.timeout, wifi_found_cb=wifi_shield_found) + ssdp_hits = ssdp.discover("urn:schemas-upnp-org:device:Basic:1", timeout=self.timeout, + wifi_found_cb=wifi_shield_found) nb_wifi_shields = len(list_id) @@ -265,8 +283,10 @@ def wifi_write(self, output): return ret_val else: if self.log: - print("Error code: %d %s" % (res_command_post.status_code, res_command_post.text)) - raise RuntimeError("Error code: %d %s" % (res_command_post.status_code, res_command_post.text)) + print("Error code: %d %s" % + (res_command_post.status_code, res_command_post.text)) + raise RuntimeError("Error code: %d %s" % ( + res_command_post.status_code, res_command_post.text)) def getSampleRate(self): return self.sample_rate @@ -281,8 +301,8 @@ def start_streaming(self, callback, lapse=-1): for every single sample that is processed Args: - callback: A callback function -- or a list of functions -- that will receive a single argument of the - OpenBCISample object captured. + callback: A callback function, or a list of functions, that will receive a single + argument of the OpenBCISample object captured. """ start_time = timeit.default_timer() @@ -296,7 +316,8 @@ def start_streaming(self, callback, lapse=-1): self.init_streaming() # while self.streaming: - # # should the board get disconnected and we could not wait for notification anymore, a reco should be attempted through timeout mechanism + # # should the board get disconnected and we could not wait for notification anymore + # # a reco should be attempted through timeout mechanism # try: # # at most we will get one sample per packet # self.waitForNotifications(1. / self.getSampleRate()) @@ -413,17 +434,18 @@ def set_channel(self, channel, toggle_position): print("Something went wrong while setting channels: " + str(e)) # See Cyton SDK for options - def set_channel_settings(self, channel, enabled=True, gain=24, input_type=0, include_bias=True, use_srb2=True, use_srb1=True): + def set_channel_settings(self, channel, enabled=True, gain=24, input_type=0, + include_bias=True, use_srb2=True, use_srb1=True): try: if channel > self.num_channels: raise ValueError('Cannot set non-existant channel') - if self.board_type == k.BOARD_GANGLION: + if self.board_type == Constants.BOARD_GANGLION: raise ValueError('Cannot use with Ganglion') ch_array = list("12345678QWERTYUI") - #defaults + # defaults command = list("x1060110X") # Set channel - command[1] = ch_array[channel-1] + command[1] = ch_array[channel - 1] # Set power down if needed (default channel enabled) if not enabled: command[2] = '1' @@ -441,7 +463,7 @@ def set_channel_settings(self, channel, enabled=True, gain=24, input_type=0, inc if gain == 12: command[3] = '5' - #TODO: Implement input type (default normal) + # TODO: Implement input type (default normal) # Set bias inclusion (default include) if not include_bias: @@ -455,10 +477,11 @@ def set_channel_settings(self, channel, enabled=True, gain=24, input_type=0, inc command_send = ''.join(command) self.wifi_write(command_send) - #Make sure to update gain in wifi - self.gains[channel-1] = gain + # Make sure to update gain in wifi + self.gains[channel - 1] = gain self.local_wifi_server.set_gains(gains=self.gains) - self.local_wifi_server.set_parser(ParseRaw(gains=self.gains, board_type=self.board_type)) + self.local_wifi_server.set_parser( + ParseRaw(gains=self.gains, board_type=self.board_type)) except ValueError as e: print("Something went wrong while setting channel settings: " + str(e)) @@ -466,40 +489,40 @@ def set_channel_settings(self, channel, enabled=True, gain=24, input_type=0, inc def set_sample_rate(self, sample_rate): """ Change sample rate """ try: - if self.board_type == k.BOARD_CYTON or self.board_type == k.BOARD_DAISY: + if self.board_type == Constants.BOARD_CYTON or self.board_type == Constants.BOARD_DAISY: if sample_rate == 250: - self.wifi_write('~6') + self.wifi_write('~6') elif sample_rate == 500: - self.wifi_write('~5') + self.wifi_write('~5') elif sample_rate == 1000: - self.wifi_write('~4') + self.wifi_write('~4') elif sample_rate == 2000: - self.wifi_write('~3') + self.wifi_write('~3') elif sample_rate == 4000: - self.wifi_write('~2') + self.wifi_write('~2') elif sample_rate == 8000: - self.wifi_write('~1') + self.wifi_write('~1') elif sample_rate == 16000: - self.wifi_write('~0') + self.wifi_write('~0') else: print("Sample rate not supported: " + str(sample_rate)) - elif self.board_type == k.BOARD_GANGLION: + elif self.board_type == Constants.BOARD_GANGLION: if sample_rate == 200: - self.wifi_write('~7') + self.wifi_write('~7') elif sample_rate == 400: - self.wifi_write('~6') + self.wifi_write('~6') elif sample_rate == 800: - self.wifi_write('~5') + self.wifi_write('~5') elif sample_rate == 1600: - self.wifi_write('~4') + self.wifi_write('~4') elif sample_rate == 3200: - self.wifi_write('~3') + self.wifi_write('~3') elif sample_rate == 6400: - self.wifi_write('~2') + self.wifi_write('~2') elif sample_rate == 12800: - self.wifi_write('~1') + self.wifi_write('~1') elif sample_rate == 25600: - self.wifi_write('~0') + self.wifi_write('~0') else: print("Sample rate not supported: " + str(sample_rate)) else: @@ -510,7 +533,7 @@ def set_sample_rate(self, sample_rate): def set_accelerometer(self, toggle_position): """ Enable / disable accelerometer """ try: - if self.board_type == k.BOARD_GANGLION: + if self.board_type == Constants.BOARD_GANGLION: # Commands to set toggle to on position if toggle_position == 1: self.wifi_write('n') @@ -559,13 +582,17 @@ def warn(self, text): if self.log: # log how many packets where sent succesfully in between warnings if self.log_packet_count: - logging.info('Data packets received:' + str(self.log_packet_count)) + logging.info('Data packets received:' + + str(self.log_packet_count)) self.log_packet_count = 0 logging.warning(text) print("Warning: %s" % text) def check_connection(self): - """ Check connection quality in term of lag and number of packets drop. Reinit connection if necessary. FIXME: parameters given to the board will be lost.""" + """ Check connection quality in term of lag and number of packets drop. + Reinit connection if necessary. + FIXME: parameters given to the board will be lost. + """ # stop checking when we're no longer streaming if not self.streaming: return @@ -579,7 +606,8 @@ def check_connection(self): self.reconnect() def reconnect(self): - """ In case of poor connection, will shut down and relaunch everything. FIXME: parameters given to the board will be lost.""" + """ In case of poor connection, will shut down and relaunch everything. + FIXME: parameters given to the board will be lost.""" self.warn('Reconnecting') self.stop() self.disconnect() @@ -596,30 +624,37 @@ def __init__(self, sock, callback=None, high_speed=True, self.daisy = daisy self.high_speed = high_speed self.last_odd_sample = OpenBCISample() - self.parser = parser if parser is not None else ParseRaw(gains=[24, 24, 24, 24, 24, 24, 24, 24]) + self.parser = parser if parser is not None else ParseRaw( + gains=[24, 24, 24, 24, 24, 24, 24, 24]) def handle_read(self): - data = self.recv(3000) # 3000 is the max data the WiFi shield is allowed to send over TCP + # 3000 is the max data the WiFi shield is allowed to send over TCP + data = self.recv(3000) if len(data) > 2: if self.high_speed: - packets = int(len(data)/33) + packets = int(len(data) / 33) raw_data_packets = [] for i in range(packets): - raw_data_packets.append(bytearray(data[i * k.RAW_PACKET_SIZE: i * k.RAW_PACKET_SIZE + k.RAW_PACKET_SIZE])) - samples = self.parser.transform_raw_data_packets_to_sample(raw_data_packets=raw_data_packets) + raw_data_packets.append( + bytearray(data[i * Constants.RAW_PACKET_SIZE: i * Constants.RAW_PACKET_SIZE + + Constants.RAW_PACKET_SIZE])) + samples = self.parser.transform_raw_data_packets_to_sample( + raw_data_packets=raw_data_packets) for sample in samples: - # if a daisy module is attached, wait to concatenate two samples (main board + daisy) - # before passing it to callback + # if a daisy module is attached, wait to concatenate two samples + # (main board + daisy) before passing it to callback if self.daisy: # odd sample: daisy sample, save for later if ~sample.sample_number % 2: self.last_odd_sample = sample - # even sample: concatenate and send if last sample was the first part, otherwise drop the packet + # even sample: concatenate and send if last sample was the first part, + # otherwise drop the packet elif sample.sample_number - 1 == self.last_odd_sample.sample_number: - # the aux data will be the average between the two samples, as the channel - # samples themselves have been averaged by the board - daisy_sample = self.parser.make_daisy_sample_object_wifi(self.last_odd_sample, sample) + # the aux data will be the average between the two samples, as the + # channel samples themselves have been averaged by the board + daisy_sample = self.parser.make_daisy_sample_object_wifi( + self.last_odd_sample, sample) if self.callback is not None: self.callback(daisy_sample) else: diff --git a/plugin_interface.py b/plugin_interface.py index ac0fbb3..70550fe 100755 --- a/plugin_interface.py +++ b/plugin_interface.py @@ -1,4 +1,3 @@ - """ Extends Yapsy IPlugin interface to pass information about the board to plugins. @@ -18,30 +17,32 @@ class PluginExample(plugintypes.IPluginExtended): ... """ +from __future__ import print_function from yapsy.IPlugin import IPlugin + class IPluginExtended(IPlugin): - # args: passed by command line - def pre_activate(self, args, sample_rate=250, eeg_channels=8, aux_channels=3, imp_channels=0): - self.args = args - self.sample_rate = sample_rate - self.eeg_channels = eeg_channels - self.aux_channels = aux_channels - self.imp_channels = imp_channels - # by default we say that activation was okay -- inherited from IPlugin - self.is_activated = True - self.activate() - # tell outside world if init went good or bad - return self.is_activated - - # inherited from IPlugin - def activate(self): - print("Plugin %s activated." % (self.__class__.__name__)) - - # inherited from IPlugin - def deactivate(self): - print("Plugin %s deactivated." % (self.__class__.__name__)) - - # plugins that require arguments should implement this method - def show_help(self): - print("I, %s, do not need any parameter." % (self.__class__.__name__)) + # args: passed by command line + def pre_activate(self, args, sample_rate=250, eeg_channels=8, aux_channels=3, imp_channels=0): + self.args = args + self.sample_rate = sample_rate + self.eeg_channels = eeg_channels + self.aux_channels = aux_channels + self.imp_channels = imp_channels + # by default we say that activation was okay -- inherited from IPlugin + self.is_activated = True + self.activate() + # tell outside world if init went good or bad + return self.is_activated + + # inherited from IPlugin + def activate(self): + print("Plugin %s activated." % (self.__class__.__name__)) + + # inherited from IPlugin + def deactivate(self): + print("Plugin %s deactivated." % (self.__class__.__name__)) + + # plugins that require arguments should implement this method + def show_help(self): + print("I, %s, do not need any parameter." % (self.__class__.__name__)) diff --git a/requirements.txt b/requirements.txt index 9df1b12..5518bd7 100755 --- a/requirements.txt +++ b/requirements.txt @@ -8,5 +8,4 @@ socketIO-client==0.6.5 websocket-client==0.32.0 wheel==0.24.0 Yapsy==1.11.23 -bluepy==1.0.5 xmltodict \ No newline at end of file diff --git a/scripts/simple_serial.py b/scripts/simple_serial.py index 314cb4b..83c3ca3 100644 --- a/scripts/simple_serial.py +++ b/scripts/simple_serial.py @@ -10,7 +10,7 @@ import pdb port = '/dev/tty.OpenBCI-DN008VTF' -#port = '/dev/tty.OpenBCI-DN0096XA' +# port = '/dev/tty.OpenBCI-DN0096XA' baud = 115200 -ser = serial.Serial(port= port, baudrate = baud, timeout = None) -pdb.set_trace() \ No newline at end of file +ser = serial.Serial(port=port, baudrate=baud, timeout=None) +pdb.set_trace() diff --git a/scripts/socket_client.py b/scripts/socket_client.py index 817ba18..7c29083 100644 --- a/scripts/socket_client.py +++ b/scripts/socket_client.py @@ -1,8 +1,10 @@ from socketIO_client import SocketIO + def on_sample(*args): print(args) + socketIO = SocketIO('10.0.1.194', 8880) socketIO.on('openbci', on_sample) socketIO.wait(seconds=10) diff --git a/scripts/stream_data.py b/scripts/stream_data.py index dde2810..afb6a74 100644 --- a/scripts/stream_data.py +++ b/scripts/stream_data.py @@ -1,4 +1,7 @@ -import sys; sys.path.append('..') # help python find cyton.py relative to scripts folder +from __future__ import print_function +import sys + +sys.path.append('..') # help python find cyton.py relative to scripts folder from openbci import cyton as bci from openbci.plugins import StreamerTCPServer import time, timeit @@ -16,10 +19,10 @@ # If > 0 will interbolate based on elapsed time SAMPLING_RATE = 256 -SERVER_PORT=12345 -SERVER_IP="localhost" +SERVER_PORT = 12345 +SERVER_IP = "localhost" -DEBUG=False +DEBUG = False # check packet drop last_id = -1 @@ -34,7 +37,8 @@ # counter to trigger duplications... leftover_duplications = 0 -tick=timeit.default_timer() +tick = timeit.default_timer() + # try to ease work for main loop class Monitor(Thread): @@ -51,7 +55,7 @@ def run(self): # check FPS + listen for new connections new_tick = timeit.default_timer() elapsed_time = new_tick - self.tick - current_samples_in = nb_samples_in + current_samples_in = nb_samples_in current_samples_out = nb_samples_out print("--- at t: ", (new_tick - self.start_tick), " ---") print("elapsed_time: ", elapsed_time) @@ -65,8 +69,8 @@ def run(self): server.check_connections() time.sleep(1) -def streamData(sample): +def streamData(sample): global last_values global tick @@ -92,20 +96,20 @@ def streamData(sample): if (SAMPLING_RATE > 0): # elapsed time since last call, update tick now = timeit.default_timer() - elapsed_time = now - tick; + elapsed_time = now - tick # now we have to compute how many times we should send data to keep up with sample rate (oversampling) leftover_duplications = SAMPLING_RATE * elapsed_time + leftover_duplications - 1 tick = now # second method with a samplin factor (depends on openbci accuracy) elif SAMPLING_FACTOR > 0: leftover_duplications = SAMPLING_FACTOR + leftover_duplications - 1 - #print "needed_duplications: ", needed_duplications, "leftover_duplications: ", leftover_duplications + # print "needed_duplications: ", needed_duplications, "leftover_duplications: ", leftover_duplications # If we need to insert values, will interpolate between current packet and last one # FIXME: ok, at the moment because we do packet per packet treatment, only handles nb_duplications == 1 for more interpolation is bad and sends nothing if (leftover_duplications > 1): leftover_duplications = leftover_duplications - 1 interpol_values = list(last_values) - for i in range(0,len(interpol_values)): + for i in range(0, len(interpol_values)): # OK, it's a very rough interpolation interpol_values[i] = (last_values[i] + sample.channel_data[i]) / 2 if DEBUG: @@ -125,6 +129,7 @@ def streamData(sample): # save current values for possible interpolation last_values = list(sample.channel_data) + if __name__ == '__main__': # init server server = StreamerTCPServer(ip=SERVER_IP, port=SERVER_PORT) diff --git a/scripts/stream_data_wifi.py b/scripts/stream_data_wifi.py index 159f56b..de30a17 100644 --- a/scripts/stream_data_wifi.py +++ b/scripts/stream_data_wifi.py @@ -1,4 +1,7 @@ -import sys; sys.path.append('..') # help python find cyton.py relative to scripts folder +from __future__ import print_function +import sys + +sys.path.append('..') # help python find cyton.py relative to scripts folder from openbci import wifi as bci import logging @@ -10,7 +13,7 @@ def printData(sample): if __name__ == '__main__': shield_name = 'OpenBCI-E2B6' - logging.basicConfig(filename="test.log",format='%(asctime)s - %(levelname)s : %(message)s',level=logging.DEBUG) + logging.basicConfig(filename="test.log", format='%(asctime)s - %(levelname)s : %(message)s', level=logging.DEBUG) logging.info('---------LOG START-------------') shield = bci.OpenBCIWiFi(shield_name=shield_name, log=True, high_speed=False) print("WiFi Shield Instantiated") diff --git a/scripts/stream_data_wifi_high_speed.py b/scripts/stream_data_wifi_high_speed.py index d2ac216..c9dd654 100644 --- a/scripts/stream_data_wifi_high_speed.py +++ b/scripts/stream_data_wifi_high_speed.py @@ -1,4 +1,7 @@ -import sys; sys.path.append('..') # help python find cyton.py relative to scripts folder +from __future__ import print_function +import sys + +sys.path.append('..') # help python find cyton.py relative to scripts folder from openbci import wifi as bci import logging @@ -9,7 +12,7 @@ def printData(sample): if __name__ == '__main__': - logging.basicConfig(filename="test.log",format='%(asctime)s - %(levelname)s : %(message)s',level=logging.DEBUG) + logging.basicConfig(filename="test.log", format='%(asctime)s - %(levelname)s : %(message)s', level=logging.DEBUG) logging.info('---------LOG START-------------') # If you don't know your IP Address, you can use shield name option # If you know IP, such as with wifi direct 192.168.4.1, then use ip_address='192.168.4.1' diff --git a/scripts/test.py b/scripts/test.py index 126814d..c5e4312 100644 --- a/scripts/test.py +++ b/scripts/test.py @@ -1,24 +1,27 @@ -import sys; sys.path.append('..') # help python find cyton.py relative to scripts folder +from __future__ import print_function +import sys + +sys.path.append('..') # help python find cyton.py relative to scripts folder from openbci import cyton as bci import logging import time + def printData(sample): - #os.system('clear') + # os.system('clear') print("----------------") - print("%f" %(sample.id)) + print("%f" % (sample.id)) print(sample.channel_data) print(sample.aux_data) print("----------------") - if __name__ == '__main__': # port = '/dev/tty.OpenBCI-DN008VTF' port = '/dev/tty.usbserial-DB00JAM0' # port = '/dev/tty.OpenBCI-DN0096XA' baud = 115200 - logging.basicConfig(filename="test.log",format='%(asctime)s - %(levelname)s : %(message)s',level=logging.DEBUG) + logging.basicConfig(filename="test.log", format='%(asctime)s - %(levelname)s : %(message)s', level=logging.DEBUG) logging.info('---------LOG START-------------') board = bci.OpenBCICyton(port=port, scaled_output=False, log=True) print("Board Instantiated") diff --git a/scripts/udp_client.py b/scripts/udp_client.py index 102d13e..b5d717f 100644 --- a/scripts/udp_client.py +++ b/scripts/udp_client.py @@ -1,14 +1,17 @@ """A sample client for the OpenBCI UDP server.""" +from __future__ import print_function import argparse + try: import cPickle as pickle except ImportError: import _pickle as pickle import json -import sys; sys.path.append('..') # help python find cyton.py relative to scripts folder -import socket +import sys +sys.path.append('..') # help python find cyton.py relative to scripts folder +import socket parser = argparse.ArgumentParser( description='Run a UDP client listening for streaming OpenBCI data.') @@ -28,28 +31,28 @@ class UDPClient(object): - def __init__(self, ip, port, json): - self.ip = ip - self.port = port - self.json = json - self.client = socket.socket( - socket.AF_INET, # Internet - socket.SOCK_DGRAM) - self.client.bind((ip, port)) - - def start_listening(self, callback=None): - while True: - data, addr = self.client.recvfrom(1024) - print("data") - if self.json: - sample = json.loads(data) - # In JSON mode we only recieve channel data. - print(data) - else: - sample = pickle.loads(data) - # Note that sample is an OpenBCISample object. - print(sample.id) - print(sample.channel_data) + def __init__(self, ip, port, json): + self.ip = ip + self.port = port + self.json = json + self.client = socket.socket( + socket.AF_INET, # Internet + socket.SOCK_DGRAM) + self.client.bind((ip, port)) + + def start_listening(self, callback=None): + while True: + data, addr = self.client.recvfrom(1024) + print("data") + if self.json: + sample = json.loads(data) + # In JSON mode we only recieve channel data. + print(data) + else: + sample = pickle.loads(data) + # Note that sample is an OpenBCISample object. + print(sample.id) + print(sample.channel_data) args = parser.parse_args() diff --git a/scripts/udp_server.py b/scripts/udp_server.py index cd3a255..277f132 100644 --- a/scripts/udp_server.py +++ b/scripts/udp_server.py @@ -7,17 +7,20 @@ - websockets """ +from __future__ import print_function import argparse + try: import cPickle as pickle except ImportError: import _pickle as pickle import json -import sys; sys.path.append('..') # help python find cyton.py relative to scripts folder +import sys + +sys.path.append('..') # help python find cyton.py relative to scripts folder from openbci import cyton as open_bci import socket - parser = argparse.ArgumentParser( description='Run a UDP server streaming OpenBCI data.') parser.add_argument( @@ -48,30 +51,30 @@ class UDPServer(object): - def __init__(self, ip, port, json): - self.ip = ip - self.port = port - self.json = json - print("Selecting raw UDP streaming. IP: ", self.ip, ", port: ", str(self.port)) - self.server = socket.socket( - socket.AF_INET, # Internet - socket.SOCK_DGRAM) + def __init__(self, ip, port, json): + self.ip = ip + self.port = port + self.json = json + print("Selecting raw UDP streaming. IP: ", self.ip, ", port: ", str(self.port)) + self.server = socket.socket( + socket.AF_INET, # Internet + socket.SOCK_DGRAM) - def send_data(self, data): - self.server.sendto(data, (self.ip, self.port)) + def send_data(self, data): + self.server.sendto(data, (self.ip, self.port)) - def handle_sample(self, sample): - if self.json: - # Just send channel data. - self.send_data(json.dumps(sample.channel_data)) - else: - # Pack up and send the whole OpenBCISample object. - self.send_data(pickle.dumps(sample)) + def handle_sample(self, sample): + if self.json: + # Just send channel data. + self.send_data(json.dumps(sample.channel_data)) + else: + # Pack up and send the whole OpenBCISample object. + self.send_data(pickle.dumps(sample)) args = parser.parse_args() obci = open_bci.OpenBCICyton(args.serial, int(args.baud)) if args.filter_data: - obci.filter_data = True + obci.filter_data = True sock_server = UDPServer(args.host, int(args.port), args.json) obci.start_streaming(sock_server.handle_sample) diff --git a/setup.py b/setup.py index bbedbb5..0612dcf 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,8 @@ from setuptools import setup, find_packages -setup(name = 'OpenBCI_Python', - version = '1.0.2', - description = 'A lib for controlling OpenBCI Devices', +setup(name='OpenBCI_Python', + version='1.0.2', + description='A lib for controlling OpenBCI Devices', author='AJ Keller', author_email='pushtheworldllc@gmail.com', license='MIT', @@ -10,5 +10,5 @@ install_requires=['numpy'], url='https://github.com/openbci/openbci_python', # use the URL to the github repo download_url='https://github.com/openbci/openbci_python/archive/v1.0.2.tar.gz', - keywords=['device', 'control', 'eeg', 'emg', 'ekg', 'ads1299', 'openbci', 'ganglion', 'cyton', 'wifi'], # arbitrary keywords + keywords=['device', 'control', 'eeg', 'emg', 'ekg', 'ads1299', 'openbci', 'ganglion', 'cyton', 'wifi'], zip_safe=False) diff --git a/test_log.py b/test_log.py index c7d06a7..aeb05d7 100644 --- a/test_log.py +++ b/test_log.py @@ -1,32 +1,35 @@ -import sys; sys.path.append('..') # help python find cyton.py relative to scripts folder -from openbci import cyton as bci -import logging +from __future__ import print_function import time +import logging +from openbci import cyton as bci +import sys -def printData(sample): - #os.system('clear') - print("----------------") - print("%f" %(sample.id)) - print(sample.channel_data) - print(sample.aux_data) - print("----------------") +sys.path.append('..') # help python find cyton.py relative to scripts folder +def printData(sample): + # os.system('clear') + print("----------------") + print("%f" % (sample.id)) + print(sample.channel_data) + print(sample.aux_data) + print("----------------") + if __name__ == '__main__': - port = '/dev/tty.usbserial-DN0096XA' - baud = 115200 - logging.basicConfig(filename="test.log",format='%(message)s',level=logging.DEBUG) - logging.info('---------LOG START-------------') - board = bci.OpenBCICyton(port=port, scaled_output=False, log=True) - - #32 bit reset - board.ser.write('v') - time.sleep(0.100) - - #connect pins to vcc - board.ser.write('p') - time.sleep(0.100) - - #board.start_streaming(printData) - board.print_packets_in() \ No newline at end of file + port = '/dev/tty.usbserial-DN0096XA' + baud = 115200 + logging.basicConfig(filename="test.log", format='%(message)s', level=logging.DEBUG) + logging.info('---------LOG START-------------') + board = bci.OpenBCICyton(port=port, scaled_output=False, log=True) + + # 32 bit reset + board.ser.write('v') + time.sleep(0.100) + + # connect pins to vcc + board.ser.write('p') + time.sleep(0.100) + + # board.start_streaming(printData) + board.print_packets_in() diff --git a/tests/test_constants.py b/tests/test_constants.py index f67219d..5db62b9 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -1,99 +1,100 @@ from unittest import TestCase, main, skip import mock -from openbci.utils import k +from openbci.utils import Constants class TestConstants(TestCase): def test_ads1299(self): - self.assertEqual(k.ADS1299_GAIN_1, 1.0) - self.assertEqual(k.ADS1299_GAIN_2, 2.0) - self.assertEqual(k.ADS1299_GAIN_4, 4.0) - self.assertEqual(k.ADS1299_GAIN_6, 6.0) - self.assertEqual(k.ADS1299_GAIN_8, 8.0) - self.assertEqual(k.ADS1299_GAIN_12, 12.0) - self.assertEqual(k.ADS1299_GAIN_24, 24.0) - self.assertEqual(k.ADS1299_VREF, 4.5) + self.assertEqual(Constants.ADS1299_GAIN_1, 1.0) + self.assertEqual(Constants.ADS1299_GAIN_2, 2.0) + self.assertEqual(Constants.ADS1299_GAIN_4, 4.0) + self.assertEqual(Constants.ADS1299_GAIN_6, 6.0) + self.assertEqual(Constants.ADS1299_GAIN_8, 8.0) + self.assertEqual(Constants.ADS1299_GAIN_12, 12.0) + self.assertEqual(Constants.ADS1299_GAIN_24, 24.0) + self.assertEqual(Constants.ADS1299_VREF, 4.5) def test_board_types(self): - self.assertEqual(k.BOARD_CYTON, 'cyton') - self.assertEqual(k.BOARD_DAISY, 'daisy') - self.assertEqual(k.BOARD_GANGLION, 'ganglion') - self.assertEqual(k.BOARD_NONE, 'none') + self.assertEqual(Constants.BOARD_CYTON, 'cyton') + self.assertEqual(Constants.BOARD_DAISY, 'daisy') + self.assertEqual(Constants.BOARD_GANGLION, 'ganglion') + self.assertEqual(Constants.BOARD_NONE, 'none') def test_cyton_variables(self): - self.assertEqual(k.CYTON_ACCEL_SCALE_FACTOR_GAIN, 0.002 / (pow(2, 4))) + self.assertEqual(Constants.CYTON_ACCEL_SCALE_FACTOR_GAIN, 0.002 / (pow(2, 4))) def test_errors(self): - self.assertEqual(k.ERROR_INVALID_BYTE_LENGTH, 'Invalid Packet Byte Length') - self.assertEqual(k.ERROR_INVALID_BYTE_START, 'Invalid Start Byte') - self.assertEqual(k.ERROR_INVALID_BYTE_STOP, 'Invalid Stop Byte') - self.assertEqual(k.ERROR_INVALID_DATA, 'Invalid data - try again') - self.assertEqual(k.ERROR_INVALID_TYPW, 'Invalid type - check comments for input type') - self.assertEqual(k.ERROR_MISSING_REGISTER_SETTING, 'Missing register setting') - self.assertEqual(k.ERROR_MISSING_REQUIRED_PROPERTY, 'Missing property in JSON') - self.assertEqual(k.ERROR_TIME_SYNC_IS_NULL, "'this.sync.curSyncObj' must not be null") - self.assertEqual(k.ERROR_TIME_SYNC_NO_COMMA, 'Missed the time sync sent confirmation. Try sync again') - self.assertEqual(k.ERROR_UNDEFINED_OR_NULL_INPUT, 'Undefined or Null Input') + self.assertEqual(Constants.ERROR_INVALID_BYTE_LENGTH, 'Invalid Packet Byte Length') + self.assertEqual(Constants.ERROR_INVALID_BYTE_START, 'Invalid Start Byte') + self.assertEqual(Constants.ERROR_INVALID_BYTE_STOP, 'Invalid Stop Byte') + self.assertEqual(Constants.ERROR_INVALID_DATA, 'Invalid data - try again') + self.assertEqual(Constants.ERROR_INVALID_TYPW, 'Invalid type - check comments for input type') + self.assertEqual(Constants.ERROR_MISSING_REGISTER_SETTING, 'Missing register setting') + self.assertEqual(Constants.ERROR_MISSING_REQUIRED_PROPERTY, 'Missing property in JSON') + self.assertEqual(Constants.ERROR_TIME_SYNC_IS_NULL, "'this.sync.curSyncObj' must not be null") + self.assertEqual(Constants.ERROR_TIME_SYNC_NO_COMMA, 'Missed the time sync sent confirmation. Try sync again') + self.assertEqual(Constants.ERROR_UNDEFINED_OR_NULL_INPUT, 'Undefined or Null Input') def test_number_of_channels(self): - self.assertEqual(k.NUMBER_OF_CHANNELS_CYTON, 8) - self.assertEqual(k.NUMBER_OF_CHANNELS_DAISY, 16) - self.assertEqual(k.NUMBER_OF_CHANNELS_GANGLION, 4) + self.assertEqual(Constants.NUMBER_OF_CHANNELS_CYTON, 8) + self.assertEqual(Constants.NUMBER_OF_CHANNELS_DAISY, 16) + self.assertEqual(Constants.NUMBER_OF_CHANNELS_GANGLION, 4) def test_protocols(self): """ Protocols """ - self.assertEqual(k.PROTOCOL_BLE, 'ble') - self.assertEqual(k.PROTOCOL_SERIAL, 'serial') - self.assertEqual(k.PROTOCOL_WIFI, 'wifi') + self.assertEqual(Constants.PROTOCOL_BLE, 'ble') + self.assertEqual(Constants.PROTOCOL_SERIAL, 'serial') + self.assertEqual(Constants.PROTOCOL_WIFI, 'wifi') def test_raw(self): - self.assertEqual(k.RAW_BYTE_START, 0xA0) - self.assertEqual(k.RAW_BYTE_STOP, 0xC0) - self.assertEqual(k.RAW_PACKET_ACCEL_NUMBER_AXIS, 3) - self.assertEqual(k.RAW_PACKET_SIZE, 33) - self.assertEqual(k.RAW_PACKET_POSITION_CHANNEL_DATA_START, 2) - self.assertEqual(k.RAW_PACKET_POSITION_CHANNEL_DATA_STOP, 25) - self.assertEqual(k.RAW_PACKET_POSITION_SAMPLE_NUMBER, 1) - self.assertEqual(k.RAW_PACKET_POSITION_START_BYTE, 0) - self.assertEqual(k.RAW_PACKET_POSITION_STOP_BYTE, 32) - self.assertEqual(k.RAW_PACKET_POSITION_START_AUX, 26) - self.assertEqual(k.RAW_PACKET_POSITION_STOP_AUX, 31) - self.assertEqual(k.RAW_PACKET_POSITION_TIME_SYNC_AUX_START, 26) - self.assertEqual(k.RAW_PACKET_POSITION_TIME_SYNC_AUX_STOP, 28) - self.assertEqual(k.RAW_PACKET_POSITION_TIME_SYNC_TIME_START, 28) - self.assertEqual(k.RAW_PACKET_POSITION_TIME_SYNC_TIME_STOP, 32) - self.assertEqual(k.RAW_PACKET_TYPE_STANDARD_ACCEL, 0) - self.assertEqual(k.RAW_PACKET_TYPE_STANDARD_RAW_AUX, 1) - self.assertEqual(k.RAW_PACKET_TYPE_USER_DEFINED_TYPE, 2) - self.assertEqual(k.RAW_PACKET_TYPE_ACCEL_TIME_SYNC_SET, 3) - self.assertEqual(k.RAW_PACKET_TYPE_ACCEL_TIME_SYNCED, 4) - self.assertEqual(k.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNC_SET, 5) - self.assertEqual(k.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNCED, 6) - self.assertEqual(k.RAW_PACKET_TYPE_IMPEDANCE, 7) + self.assertEqual(Constants.RAW_BYTE_START, 0xA0) + self.assertEqual(Constants.RAW_BYTE_STOP, 0xC0) + self.assertEqual(Constants.RAW_PACKET_ACCEL_NUMBER_AXIS, 3) + self.assertEqual(Constants.RAW_PACKET_SIZE, 33) + self.assertEqual(Constants.RAW_PACKET_POSITION_CHANNEL_DATA_START, 2) + self.assertEqual(Constants.RAW_PACKET_POSITION_CHANNEL_DATA_STOP, 25) + self.assertEqual(Constants.RAW_PACKET_POSITION_SAMPLE_NUMBER, 1) + self.assertEqual(Constants.RAW_PACKET_POSITION_START_BYTE, 0) + self.assertEqual(Constants.RAW_PACKET_POSITION_STOP_BYTE, 32) + self.assertEqual(Constants.RAW_PACKET_POSITION_START_AUX, 26) + self.assertEqual(Constants.RAW_PACKET_POSITION_STOP_AUX, 31) + self.assertEqual(Constants.RAW_PACKET_POSITION_TIME_SYNC_AUX_START, 26) + self.assertEqual(Constants.RAW_PACKET_POSITION_TIME_SYNC_AUX_STOP, 28) + self.assertEqual(Constants.RAW_PACKET_POSITION_TIME_SYNC_TIME_START, 28) + self.assertEqual(Constants.RAW_PACKET_POSITION_TIME_SYNC_TIME_STOP, 32) + self.assertEqual(Constants.RAW_PACKET_TYPE_STANDARD_ACCEL, 0) + self.assertEqual(Constants.RAW_PACKET_TYPE_STANDARD_RAW_AUX, 1) + self.assertEqual(Constants.RAW_PACKET_TYPE_USER_DEFINED_TYPE, 2) + self.assertEqual(Constants.RAW_PACKET_TYPE_ACCEL_TIME_SYNC_SET, 3) + self.assertEqual(Constants.RAW_PACKET_TYPE_ACCEL_TIME_SYNCED, 4) + self.assertEqual(Constants.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNC_SET, 5) + self.assertEqual(Constants.RAW_PACKET_TYPE_RAW_AUX_TIME_SYNCED, 6) + self.assertEqual(Constants.RAW_PACKET_TYPE_IMPEDANCE, 7) def test_sample_number_max(self): - self.assertEqual(k.SAMPLE_NUMBER_MAX_CYTON, 255) - self.assertEqual(k.SAMPLE_NUMBER_MAX_GANGLION, 200) + self.assertEqual(Constants.SAMPLE_NUMBER_MAX_CYTON, 255) + self.assertEqual(Constants.SAMPLE_NUMBER_MAX_GANGLION, 200) def test_sample_rates(self): - self.assertEqual(k.SAMPLE_RATE_1000, 1000) - self.assertEqual(k.SAMPLE_RATE_125, 125) - self.assertEqual(k.SAMPLE_RATE_12800, 12800) - self.assertEqual(k.SAMPLE_RATE_1600, 1600) - self.assertEqual(k.SAMPLE_RATE_16000, 16000) - self.assertEqual(k.SAMPLE_RATE_200, 200) - self.assertEqual(k.SAMPLE_RATE_2000, 2000) - self.assertEqual(k.SAMPLE_RATE_250, 250) - self.assertEqual(k.SAMPLE_RATE_25600, 25600) - self.assertEqual(k.SAMPLE_RATE_3200, 3200) - self.assertEqual(k.SAMPLE_RATE_400, 400) - self.assertEqual(k.SAMPLE_RATE_4000, 4000) - self.assertEqual(k.SAMPLE_RATE_500, 500) - self.assertEqual(k.SAMPLE_RATE_6400, 6400) - self.assertEqual(k.SAMPLE_RATE_800, 800) - self.assertEqual(k.SAMPLE_RATE_8000, 8000) + self.assertEqual(Constants.SAMPLE_RATE_1000, 1000) + self.assertEqual(Constants.SAMPLE_RATE_125, 125) + self.assertEqual(Constants.SAMPLE_RATE_12800, 12800) + self.assertEqual(Constants.SAMPLE_RATE_1600, 1600) + self.assertEqual(Constants.SAMPLE_RATE_16000, 16000) + self.assertEqual(Constants.SAMPLE_RATE_200, 200) + self.assertEqual(Constants.SAMPLE_RATE_2000, 2000) + self.assertEqual(Constants.SAMPLE_RATE_250, 250) + self.assertEqual(Constants.SAMPLE_RATE_25600, 25600) + self.assertEqual(Constants.SAMPLE_RATE_3200, 3200) + self.assertEqual(Constants.SAMPLE_RATE_400, 400) + self.assertEqual(Constants.SAMPLE_RATE_4000, 4000) + self.assertEqual(Constants.SAMPLE_RATE_500, 500) + self.assertEqual(Constants.SAMPLE_RATE_6400, 6400) + self.assertEqual(Constants.SAMPLE_RATE_800, 800) + self.assertEqual(Constants.SAMPLE_RATE_8000, 8000) + if __name__ == '__main__': main() diff --git a/tests/test_parse.py b/tests/test_parse.py index 773e504..c7c17e5 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -1,7 +1,7 @@ from unittest import TestCase, main, skip import mock -from openbci.utils import (k, +from openbci.utils import (Constants, OpenBCISample, ParseRaw, sample_packet, @@ -26,7 +26,7 @@ def test_get_channel_data_array(self): scale_factors = parser.get_ads1299_scale_factors(expected_gains) expected_channel_data = [] - for i in range(k.NUMBER_OF_CHANNELS_CYTON): + for i in range(Constants.NUMBER_OF_CHANNELS_CYTON): expected_channel_data.append(scale_factors[i] * (i + 1)) parser.raw_data_to_sample.raw_data_packet = data @@ -43,8 +43,8 @@ def test_get_data_array_accel(self): parser = ParseRaw(gains=[24, 24, 24, 24, 24, 24, 24, 24], scaled_output=True) expected_accel_data = [] - for i in range(k.RAW_PACKET_ACCEL_NUMBER_AXIS): - expected_accel_data.append(k.CYTON_ACCEL_SCALE_FACTOR_GAIN * i) + for i in range(Constants.RAW_PACKET_ACCEL_NUMBER_AXIS): + expected_accel_data.append(Constants.CYTON_ACCEL_SCALE_FACTOR_GAIN * i) parser.raw_data_to_sample.raw_data_packet = data @@ -109,7 +109,7 @@ def test_interpret_24_bit_as_int_32(self): 'converts a large negative number') def test_parse_raw_init(self): - expected_board_type = k.BOARD_DAISY + expected_board_type = Constants.BOARD_DAISY expected_gains = [24, 24, 24, 24, 24, 24, 24, 24] expected_log = True expected_micro_volts = True @@ -171,8 +171,8 @@ def test_parse_packet_standard_accel(self): for i in range(len(sample.channel_data)): self.assertEqual(sample.channel_data[i], expected_scale_factor * (i + 1)) for i in range(len(sample.accel_data)): - self.assertEqual(sample.accel_data[i], k.CYTON_ACCEL_SCALE_FACTOR_GAIN * i) - self.assertEqual(sample.packet_type, k.RAW_PACKET_TYPE_STANDARD_ACCEL) + self.assertEqual(sample.accel_data[i], Constants.CYTON_ACCEL_SCALE_FACTOR_GAIN * i) + self.assertEqual(sample.packet_type, Constants.RAW_PACKET_TYPE_STANDARD_ACCEL) self.assertEqual(sample.sample_number, 0x45) self.assertEqual(sample.start_byte, 0xA0) self.assertEqual(sample.stop_byte, 0xC0) @@ -261,13 +261,13 @@ def test_make_daisy_sample_object_wifi(self): upper_sample_object.aux_data = [3, 4, 5] upper_sample_object.timestamp = 8 - daisy_sample_object = parser.make_daisy_sample_object_wifi(lower_sample_object, upper_sample_object); + daisy_sample_object = parser.make_daisy_sample_object_wifi(lower_sample_object, upper_sample_object) # should have valid object true self.assertTrue(daisy_sample_object.valid) # should make a channelData array 16 elements long - self.assertEqual(len(daisy_sample_object.channel_data), k.NUMBER_OF_CHANNELS_DAISY) + self.assertEqual(len(daisy_sample_object.channel_data), Constants.NUMBER_OF_CHANNELS_DAISY) # should make a channelData array with lower array in front of upper array for i in range(16): diff --git a/user.py b/user.py index 56d855c..01bb3a6 100644 --- a/user.py +++ b/user.py @@ -1,4 +1,6 @@ #!/usr/bin/env python2.7 +from __future__ import print_function +from yapsy.PluginManager import PluginManager import argparse # new in Python2.7 import atexit import logging @@ -9,16 +11,15 @@ logging.basicConfig(level=logging.ERROR) -from yapsy.PluginManager import PluginManager # Load the plugins from the plugin directory. manager = PluginManager() if __name__ == '__main__': - print ("------------user.py-------------") + print("------------user.py-------------") parser = argparse.ArgumentParser(description="OpenBCI 'user'") - parser.add_argument('--board', default="cyton", + parser.add_argument('--board', default="cyton", help="Choose between [cyton] and [ganglion] boards.") parser.add_argument('-l', '--list', action='store_true', help="List available plugins.") @@ -26,7 +27,8 @@ help="Show more information about a plugin.") parser.add_argument('-p', '--port', help="For Cyton, port to connect to OpenBCI Dongle " + - "( ex /dev/ttyUSB0 or /dev/tty.usbserial-* ). For Ganglion, MAC address of the board. For both, AUTO to attempt auto-detection.") + "( ex /dev/ttyUSB0 or /dev/tty.usbserial-* ). " + + "For Ganglion, MAC address of the board. For both, AUTO to attempt auto-detection.") parser.set_defaults(port="AUTO") # baud rate is not currently used parser.add_argument('-b', '--baud', default=115200, type=int, @@ -54,14 +56,16 @@ args = parser.parse_args() - if not(args.add): - print ("WARNING: no plugin selected, you will only be able to communicate with the board. You should select at least one plugin with '--add [plugin_name]'. Use '--list' to show available plugins or '--info [plugin_name]' to get more information.") + if not args.add: + print("WARNING: no plugin selected, you will only be able to communicate with the board. " + "You should select at least one plugin with '--add [plugin_name]'. " + "Use '--list' to show available plugins or '--info [plugin_name]' to get more information.") if args.board == "cyton": - print ("Board type: OpenBCI Cyton (v3 API)") + print("Board type: OpenBCI Cyton (v3 API)") import openbci.cyton as bci elif args.board == "ganglion": - print ("Board type: OpenBCI Ganglion") + print("Board type: OpenBCI Ganglion") import openbci.ganglion as bci else: raise ValueError('Board type %r was not recognized. Known are 3 and 4' % args.board) @@ -72,7 +76,7 @@ args.port = None else: print("Port: ", args.port) - + plugins_paths = ["openbci/plugins"] if args.plugins_path: plugins_paths += args.plugins_path @@ -81,9 +85,9 @@ # Print list of available plugins and exit if args.list: - print ("Available plugins:") + print("Available plugins:") for plugin in manager.getAllPlugins(): - print ("\t- " + plugin.name) + print("\t- " + plugin.name) exit() # User wants more info about a plugin... @@ -91,26 +95,28 @@ plugin = manager.getPluginByName(args.info) if plugin == None: # eg: if an import fail inside a plugin, yapsy skip it - print ("Error: [ " + args.info + " ] not found or could not be loaded. Check name and requirements.") + print("Error: [ " + args.info + + " ] not found or could not be loaded. Check name and requirements.") else: - print (plugin.description) + print(plugin.description) plugin.plugin_object.show_help() exit() - print ("\n------------SETTINGS-------------") - print ("Notch filtering:" + str(args.filtering)) + print("\n------------SETTINGS-------------") + print("Notch filtering:" + str(args.filtering)) # Logging if args.log: - print ("Logging Enabled: " + str(args.log)) - logging.basicConfig(filename="OBCI.log", format='%(asctime)s - %(levelname)s : %(message)s', level=logging.DEBUG) + print("Logging Enabled: " + str(args.log)) + logging.basicConfig(filename="OBCI.log", format='%(asctime)s - %(levelname)s : %(message)s', + level=logging.DEBUG) logging.getLogger('yapsy').setLevel(logging.DEBUG) logging.info('---------LOG START-------------') logging.info(args) else: - print ("user.py: Logging Disabled.") + print("user.py: Logging Disabled.") - print ("\n-------INSTANTIATING BOARD-------") + print("\n-------INSTANTIATING BOARD-------") if args.board == "cyton": board = bci.OpenBCICyton(port=args.port, baud=args.baud, @@ -127,19 +133,19 @@ # Info about effective number of channels and sampling rate if board.daisy: - print ("Force daisy mode:") + print("Force daisy mode:") else: - print ("No daisy:") - print (board.getNbEEGChannels(), "EEG channels and", board.getNbAUXChannels(), "AUX channels at", board.getSampleRate(), "Hz.") + print("No daisy:") + print(board.getNbEEGChannels(), "EEG channels and", board.getNbAUXChannels(), "AUX channels at", + board.getSampleRate(), "Hz.") - print ("\n------------PLUGINS--------------") + print("\n------------PLUGINS--------------") # Loop round the plugins and print their names. - print ("Found plugins:") + print("Found plugins:") for plugin in manager.getAllPlugins(): - print ("[ " + plugin.name + " ]") + print("[ " + plugin.name + " ]") print("\n") - # Fetch plugins, try to activate them, add to the list if OK plug_list = [] callback_list = [] @@ -152,13 +158,16 @@ plug = manager.getPluginByName(plug_name) if plug == None: # eg: if an import fail inside a plugin, yapsy skip it - print ("Error: [ " + plug_name + " ] not found or could not be loaded. Check name and requirements.") + print("Error: [ " + plug_name + " ] not found or could not be loaded. Check name and requirements.") else: - print ("\nActivating [ " + plug_name + " ] plugin...") - if not plug.plugin_object.pre_activate(plug_args, sample_rate=board.getSampleRate(), eeg_channels=board.getNbEEGChannels(), aux_channels=board.getNbAUXChannels(), imp_channels=board.getNbImpChannels()): - print ("Error while activating [ " + plug_name + " ], check output for more info.") + print("\nActivating [ " + plug_name + " ] plugin...") + if not plug.plugin_object.pre_activate(plug_args, sample_rate=board.getSampleRate(), + eeg_channels=board.getNbEEGChannels(), + aux_channels=board.getNbAUXChannels(), + imp_channels=board.getNbImpChannels()): + print("Error while activating [ " + plug_name + " ], check output for more info.") else: - print ("Plugin [ " + plug_name + "] added to the list") + print("Plugin [ " + plug_name + "] added to the list") plug_list.append(plug.plugin_object) callback_list.append(plug.plugin_object) @@ -169,15 +178,15 @@ def cleanUp(): board.disconnect() - print ("Deactivating Plugins...") + print("Deactivating Plugins...") for plug in plug_list: plug.deactivate() - print ("User.py exiting...") + print("User.py exiting...") atexit.register(cleanUp) - print ("--------------INFO---------------") - print ("User serial interface enabled...\n\ + print("--------------INFO---------------") + print("User serial interface enabled...\n\ View command map at http://docs.openbci.com.\n\ Type /start to run (/startimp for impedance \n\ checking, if supported) -- and /stop\n\ @@ -199,71 +208,71 @@ def cleanUp(): # d: Channels settings back to default s = s + 'd' - while(s != "/exit"): + while s != "/exit": # Send char and wait for registers to set - if (not s): + if not s: pass - elif("help" in s): - print ("View command map at: \ + elif "help" in s: + print("View command map at: \ http://docs.openbci.com/software/01-OpenBCI_SDK.\n\ For user interface: read README or view \ https://github.com/OpenBCI/OpenBCI_Python") elif board.streaming and s != "/stop": - print ("Error: the board is currently streaming data, please type '/stop' before issuing new commands.") + print("Error: the board is currently streaming data, please type '/stop' before issuing new commands.") else: # read silently incoming packet if set (used when stream is stopped) flush = False - if('/' == s[0]): + if '/' == s[0]: s = s[1:] rec = False # current command is recognized or fot - if("T:" in s): - lapse = int(s[string.find(s, "T:")+2:]) + if "T:" in s: + lapse = int(s[string.find(s, "T:") + 2:]) rec = True - elif("t:" in s): - lapse = int(s[string.find(s, "t:")+2:]) + elif "t:" in s: + lapse = int(s[string.find(s, "t:") + 2:]) rec = True else: lapse = -1 - if('startimp' in s): + if 'startimp' in s: if board.getBoardType() == "cyton": - print ("Impedance checking not supported on cyton.") + print("Impedance checking not supported on cyton.") else: board.setImpedance(True) - if(fun != None): + if (fun != None): # start streaming in a separate thread so we could always send commands in here boardThread = threading.Thread(target=board.start_streaming, args=(fun, lapse)) - boardThread.daemon = True # will stop on exit + boardThread.daemon = True # will stop on exit try: boardThread.start() except: - raise + raise else: - print ("No function loaded") + print("No function loaded") rec = True - - elif("start" in s): + + elif "start" in s: board.setImpedance(False) - if(fun != None): + if fun != None: # start streaming in a separate thread so we could always send commands in here boardThread = threading.Thread(target=board.start_streaming, args=(fun, lapse)) - boardThread.daemon = True # will stop on exit + boardThread.daemon = True # will stop on exit try: boardThread.start() except: - raise + raise else: - print ("No function loaded") + print("No function loaded") rec = True - elif('test' in s): - test = int(s[s.find("test")+4:]) + elif 'test' in s: + test = int(s[s.find("test") + 4:]) board.test_signal(test) rec = True - elif('stop' in s): + elif 'stop' in s: board.stop() rec = True flush = True @@ -279,25 +288,27 @@ def cleanUp(): time.sleep(0.100) line = '' - time.sleep(0.1) #Wait to see if the board has anything to report - # The Cyton nicely return incoming packets -- here supposedly messages -- whereas the Ganglion prints incoming ASCII message by itself + time.sleep(0.1) # Wait to see if the board has anything to report + # The Cyton nicely return incoming packets -- here supposedly messages + # whereas the Ganglion prints incoming ASCII message by itself if board.getBoardType() == "cyton": - while board.ser_inWaiting(): - c = board.ser_read().decode('utf-8', errors='replace') # we're supposed to get UTF8 text, but the board might behave otherwise - line += c - time.sleep(0.001) - if (c == '\n') and not flush: - print('%\t'+line[:-1]) - line = '' + while board.ser_inWaiting(): + # we're supposed to get UTF8 text, but the board might behave otherwise + c = board.ser_read().decode('utf-8', errors='replace') + line += c + time.sleep(0.001) + if (c == '\n') and not flush: + print('%\t' + line[:-1]) + line = '' elif board.getBoardType() == "ganglion": - while board.ser_inWaiting(): - board.waitForNotifications(0.001) + while board.ser_inWaiting(): + board.waitForNotifications(0.001) if not flush: print(line) # Take user input - #s = input('--> ') + # s = input('--> ') if sys.hexversion > 0x03000000: s = input('--> ') else: