From ce3e4dc56c4d7f740356d495cf56af64e8ff8dc9 Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Wed, 17 Oct 2018 14:53:57 -0500 Subject: [PATCH 1/9] Run pylint from Travis --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 74696a0..9e7cb84 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,10 +22,11 @@ install: # Install project requirements - pip install -r requirements.txt # Install test and coverage requirements - - pip install codecov mock nose coverage + - pip install codecov mock nose coverage pylint # Run tests script: - nosetests --with-coverage --cover-package=openbci after_success: - codecov + - pylint openbci From f7d4d4e4ce045de0e0c83b2a2e510228fd1bd8d5 Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Wed, 17 Oct 2018 15:14:55 -0500 Subject: [PATCH 2/9] Code formatting improvements --- externals/mne_openbci.py | 3 + openbci/__init__.py | 2 - openbci/cyton.py | 1072 +++++++++--------- openbci/ganglion.py | 1406 ++++++++++++------------ openbci/plugins/__init__.py | 1 - openbci/plugins/csv_collect.py | 105 +- openbci/plugins/noise_test.py | 52 +- openbci/plugins/print.py | 46 +- openbci/plugins/sample_rate.py | 80 +- openbci/plugins/streamer_lsl.py | 105 +- openbci/plugins/streamer_osc.py | 85 +- openbci/plugins/streamer_tcp_server.py | 26 +- openbci/plugins/udp_server.py | 75 +- openbci/utils/parse.py | 14 +- openbci/utils/ssdp.py | 13 +- openbci/utils/utilities.py | 41 +- openbci/wifi.py | 78 +- plugin_interface.py | 50 +- scripts/simple_serial.py | 6 +- scripts/socket_client.py | 2 + scripts/stream_data.py | 22 +- scripts/stream_data_wifi.py | 6 +- scripts/stream_data_wifi_high_speed.py | 6 +- scripts/test.py | 12 +- scripts/udp_client.py | 50 +- scripts/udp_server.py | 42 +- setup.py | 9 +- test_log.py | 50 +- tests/test_constants.py | 1 + user.py | 88 +- 30 files changed, 1815 insertions(+), 1733 deletions(-) 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..f41cb40 100644 --- a/openbci/__init__.py +++ b/openbci/__init__.py @@ -1,9 +1,7 @@ - from .cyton import OpenBCICyton from .ganglion import OpenBCIGanglion from .plugins import * from .utils import * from .wifi import OpenBCIWiFi - __version__ = "1.0.0" diff --git a/openbci/cyton.py b/openbci/cyton.py index c0c6016..1bbc1c1 100644 --- a/openbci/cyton.py +++ b/openbci/cyton.py @@ -31,10 +31,10 @@ 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,556 +57,554 @@ 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)) - 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) - 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)) + 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) + 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: - 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 = 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 + 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: + 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): + """ + + 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: + 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 + 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 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: - packet_str = packet_str + '.' + "%03d"%(b) + '|INV'; - #Reset - self.attempt_reconnect = True - - - else: - print(b) - if b == END_BYTE: - skipped_str = skipped_str + '|END|' + 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: - skipped_str = skipped_str + "%03d"%(b) + '.' + 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 a78a44f..90a9695 100644 --- a/openbci/ganglion.py +++ b/openbci/ganglion.py @@ -25,7 +25,8 @@ def handle_sample(sample): import glob # local bluepy should take precedence import sys -sys.path.insert(0,"bluepy/bluepy") + +sys.path.insert(0, "bluepy/bluepy") STUB_BTLE = False @@ -35,8 +36,7 @@ def handle_sample(sample): DefaultDelegate = object STUB_BTLE = True else: - from bluepy.btle import Scanner, DefaultDelegate, Peripheral - + from bluepy.btle import Scanner, DefaultDelegate, Peripheral SAMPLE_RATE = 200.0 # Hz scale_fac_uVolts_per_count = 1200 / (8388607.0 * 1.5 * 51.0) @@ -58,768 +58,782 @@ 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 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 + 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 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.") + + 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 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] + + 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..9fddc46 100755 --- a/openbci/plugins/csv_collect.py +++ b/openbci/plugins/csv_collect.py @@ -4,56 +4,57 @@ 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..2149748 100755 --- a/openbci/plugins/noise_test.py +++ b/openbci/plugins/noise_test.py @@ -2,39 +2,35 @@ 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 \ + 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..151c547 100755 --- a/openbci/plugins/print.py +++ b/openbci/plugins/print.py @@ -1,26 +1,28 @@ 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..012c160 100755 --- a/openbci/plugins/sample_rate.py +++ b/openbci/plugins/sample_rate.py @@ -7,46 +7,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..cdac8c6 100755 --- a/openbci/plugins/streamer_lsl.py +++ b/openbci/plugins/streamer_lsl.py @@ -1,61 +1,64 @@ # 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 +import sys + +sys.path.append('lib') # help python find pylsl relative to this example program from pylsl import StreamInfo, StreamOutlet import plugin_interface as plugintypes + # 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]]]]]] + # 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..5e94e37 100755 --- a/openbci/plugins/streamer_osc.py +++ b/openbci/plugins/streamer_osc.py @@ -1,54 +1,53 @@ - # 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) 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]]] + """ + 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..4c583d9 100755 --- a/openbci/plugins/streamer_tcp_server.py +++ b/openbci/plugins/streamer_tcp_server.py @@ -2,6 +2,7 @@ import socket, select, struct, 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 # TODO: does not listen for anything at the moment, could use it to set options @@ -9,6 +10,7 @@ # Handling new client in separate thread class MonitorStreamer(Thread): """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 +39,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 +72,18 @@ 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) + 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 +93,15 @@ def deactivate(self): sock.send("closing!\n") # at this point don't bother if message not sent except: - continue + continue sock.close(); # close server socket 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 +110,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 diff --git a/openbci/plugins/udp_server.py b/openbci/plugins/udp_server.py index 946e7cc..c0882e3 100755 --- a/openbci/plugins/udp_server.py +++ b/openbci/plugins/udp_server.py @@ -16,26 +16,27 @@ 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]) # print("---------------------------------") # print(sample_string) # print("---------------------------------") - + # # DEBBUGING # # try: # # sample_string.decode('ascii') @@ -46,38 +47,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..6d87473 100644 --- a/openbci/utils/parse.py +++ b/openbci/utils/parse.py @@ -64,7 +64,9 @@ 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]) + 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 @@ -72,7 +74,9 @@ def get_channel_data_array(self, raw_data_to_sample): 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]) + 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 @@ -224,7 +228,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 +260,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 +274,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 +317,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..0f0729c 100644 --- a/openbci/utils/utilities.py +++ b/openbci/utils/utilities.py @@ -25,40 +25,61 @@ 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(k.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(k.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(k.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(k.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(k.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(k.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(k.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(k.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(k.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(k.OBCIStreamPacketUserDefinedType)]) diff --git a/openbci/wifi.py b/openbci/wifi.py index 790a914..cbc97c9 100755 --- a/openbci/wifi.py +++ b/openbci/wifi.py @@ -23,6 +23,7 @@ def handle_sample(sample): import re import socket import timeit + try: import urllib2 except ImportError: @@ -157,7 +158,8 @@ 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: @@ -181,13 +183,13 @@ def connect(self): 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']: @@ -204,7 +206,8 @@ def init_streaming(self): 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.""" @@ -235,7 +238,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) @@ -245,9 +249,9 @@ def wifi_shield_found(response): if nb_wifi_shields > 1: print( - "Found " + str(nb_wifi_shields) + - ", selecting first named: " + list_id[0] + - " with IPV4: " + list_ip[0]) + "Found " + str(nb_wifi_shields) + + ", selecting first named: " + list_id[0] + + " with IPV4: " + list_ip[0]) return list_ip[0] def wifi_write(self, output): @@ -413,17 +417,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: 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 +446,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,8 +460,8 @@ 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)) @@ -468,38 +473,38 @@ def set_sample_rate(self, sample_rate): try: if self.board_type == k.BOARD_CYTON or self.board_type == k.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: 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: @@ -602,10 +607,11 @@ def handle_read(self): data = self.recv(3000) # 3000 is the max data the WiFi shield is allowed to send over TCP 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])) + 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) for sample in samples: diff --git a/plugin_interface.py b/plugin_interface.py index ac0fbb3..74c2e69 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. @@ -20,28 +19,29 @@ class PluginExample(plugintypes.IPluginExtended): 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/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..fc1bd66 100644 --- a/scripts/stream_data.py +++ b/scripts/stream_data.py @@ -1,4 +1,6 @@ -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 bci from openbci.plugins import StreamerTCPServer import time, timeit @@ -16,10 +18,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 +36,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 +54,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 +68,8 @@ def run(self): server.check_connections() time.sleep(1) -def streamData(sample): +def streamData(sample): global last_values global tick @@ -99,13 +102,13 @@ def streamData(sample): # 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 +128,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..9acd91e 100644 --- a/scripts/stream_data_wifi.py +++ b/scripts/stream_data_wifi.py @@ -1,4 +1,6 @@ -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 wifi as bci import logging @@ -10,7 +12,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..8f2b3ec 100644 --- a/scripts/stream_data_wifi_high_speed.py +++ b/scripts/stream_data_wifi_high_speed.py @@ -1,4 +1,6 @@ -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 wifi as bci import logging @@ -9,7 +11,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..4871b92 100644 --- a/scripts/test.py +++ b/scripts/test.py @@ -1,24 +1,26 @@ -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 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..9dd37dc 100644 --- a/scripts/udp_client.py +++ b/scripts/udp_client.py @@ -1,14 +1,16 @@ """A sample client for the OpenBCI UDP server.""" 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 +30,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..1f01f04 100644 --- a/scripts/udp_server.py +++ b/scripts/udp_server.py @@ -8,16 +8,18 @@ """ 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 +50,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 f14e87c..2ce151b 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,8 @@ from setuptools import setup, find_packages -setup(name = 'OpenBCI_Python', - version = '1.0.1', - description = 'A lib for controlling OpenBCI Devices', +setup(name='OpenBCI_Python', + version='1.0.1', + description='A lib for controlling OpenBCI Devices', author='AJ Keller', author_email='pushtheworldllc@gmail.com', license='MIT', @@ -10,5 +10,6 @@ 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.1.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'], + # arbitrary keywords zip_safe=False) diff --git a/test_log.py b/test_log.py index c7d06a7..59c1cdb 100644 --- a/test_log.py +++ b/test_log.py @@ -1,32 +1,34 @@ -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 bci import logging import time -def printData(sample): - #os.system('clear') - print("----------------") - print("%f" %(sample.id)) - print(sample.channel_data) - print(sample.aux_data) - print("----------------") +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..27f3157 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -95,5 +95,6 @@ def test_sample_rates(self): self.assertEqual(k.SAMPLE_RATE_800, 800) self.assertEqual(k.SAMPLE_RATE_8000, 8000) + if __name__ == '__main__': main() diff --git a/user.py b/user.py index cc5bc7d..1f7ed6a 100644 --- a/user.py +++ b/user.py @@ -18,7 +18,7 @@ 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 +26,7 @@ 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,8 +54,9 @@ 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)") @@ -72,7 +73,7 @@ args.port = None else: print("Port: ", args.port) - + plugins_paths = ["plugins"] if args.plugins_path: plugins_paths += args.plugins_path @@ -91,7 +92,7 @@ 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) plugin.plugin_object.show_help() @@ -103,7 +104,8 @@ # 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) + 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) @@ -130,7 +132,8 @@ print ("Force daisy mode:") else: print ("No daisy:") - print (board.getNbEEGChannels(), "EEG channels and", board.getNbAUXChannels(), "AUX channels at", board.getSampleRate(), "Hz.") + print (board.getNbEEGChannels(), "EEG channels and", board.getNbAUXChannels(), "AUX channels at", + board.getSampleRate(), "Hz.") print ("\n------------PLUGINS--------------") # Loop round the plugins and print their names. @@ -139,7 +142,6 @@ print ("[ " + plugin.name + " ]") print("\n") - # Fetch plugins, try to activate them, add to the list if OK plug_list = [] callback_list = [] @@ -155,7 +157,10 @@ 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()): + 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") @@ -167,6 +172,7 @@ else: fun = callback_list + def cleanUp(): board.disconnect() print ("Deactivating Plugins...") @@ -174,6 +180,7 @@ def cleanUp(): plug.deactivate() print ("User.py exiting...") + atexit.register(cleanUp) print ("--------------INFO---------------") @@ -199,11 +206,11 @@ 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): pass - elif("help" in s): + 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 \ @@ -215,55 +222,55 @@ def cleanUp(): # 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.") 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") 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") 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 +286,26 @@ def cleanUp(): time.sleep(0.100) line = '' - time.sleep(0.1) #Wait to see if the board has anything to report + 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(): + 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 = '' 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: From f8f2d1703d408b425b0757e829a8884d91ae2061 Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Wed, 17 Oct 2018 18:38:05 -0500 Subject: [PATCH 3/9] Some autopep8 changes --- openbci/cyton.py | 97 ++++++++++++++------------ openbci/ganglion.py | 86 +++++++++++++---------- openbci/plugins/csv_collect.py | 2 +- openbci/plugins/noise_test.py | 4 +- openbci/plugins/print.py | 4 +- openbci/plugins/streamer_lsl.py | 17 +++-- openbci/plugins/streamer_osc.py | 9 +-- openbci/plugins/streamer_tcp_server.py | 12 ++-- openbci/plugins/udp_server.py | 2 +- openbci/utils/parse.py | 14 ++-- openbci/wifi.py | 62 ++++++++++------ test_log.py | 6 +- user.py | 50 +++++++------ 13 files changed, 210 insertions(+), 155 deletions(-) diff --git a/openbci/cyton.py b/openbci/cyton.py index 1bbc1c1..d632b07 100644 --- a/openbci/cyton.py +++ b/openbci/cyton.py @@ -33,8 +33,10 @@ def handle_sample(sample): 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 +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: @@ -94,8 +96,10 @@ def __init__(self, port=None, baud=115200, filter_data=True, 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* + # 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 @@ -127,7 +131,7 @@ def ser_read(self): def ser_inWaiting(self): """Access serial port object for inWaiting""" - return self.ser.inWaiting(); + return self.ser.inWaiting() def getSampleRate(self): if self.daisy: @@ -181,7 +185,8 @@ def start_streaming(self, callback, lapse=-1): # 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) + 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: @@ -191,9 +196,9 @@ def start_streaming(self, callback, lapse=-1): call(sample) if (lapse > 0 and timeit.default_timer() - start_time > lapse): - self.stop(); + self.stop() if self.log: - self.log_packet_count = self.log_packet_count + 1; + self.log_packet_count = self.log_packet_count + 1 """ PARSER: @@ -225,10 +230,12 @@ def read(n): 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.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 @@ -268,18 +275,18 @@ def read(n): # short = h acc = struct.unpack('>h', read(2))[0] - log_bytes_in = log_bytes_in + '|' + str(acc); + 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; + 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); + 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) @@ -288,7 +295,7 @@ def read(n): else: self.warn("ID:<%d> instead of <%s>" % (packet_id, val, END_BYTE)) - logging.debug(log_bytes_in); + logging.debug(log_bytes_in) self.packets_dropped = self.packets_dropped + 1 """ @@ -322,8 +329,9 @@ 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.info('Data packets received:' + + str(self.log_packet_count)) + self.log_packet_count = 0 logging.warning(text) print("Warning: %s" % text) @@ -343,10 +351,11 @@ def print_incoming_text(self): 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') # we're supposed to get UTF8 text, but the board might behave otherwise + errors='replace') line += c - print(line); + print(line) else: self.warn("No Message") @@ -365,8 +374,9 @@ def openbci_id(self, serial): 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') # we're supposed to get UTF8 text, but the board might behave otherwise + errors='replace') line += c if "OpenBCI" in line: return True @@ -375,7 +385,7 @@ def openbci_id(self, serial): def print_register_settings(self): self.ser.write(b'?') time.sleep(0.5) - self.print_incoming_text(); + self.print_incoming_text() # DEBBUGING: Prints individual incoming bytes def print_bytes_in(self): @@ -383,7 +393,7 @@ def print_bytes_in(self): self.ser.write(b'b') self.streaming = True while self.streaming: - print(struct.unpack('B', self.ser.read())[0]); + 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) @@ -391,7 +401,7 @@ def print_bytes_in(self): def print_packets_in(self): while self.streaming: - b = struct.unpack('B', self.ser.read())[0]; + b = struct.unpack('B', self.ser.read())[0] if b == START_BYTE: self.attempt_reconnect = False @@ -399,42 +409,41 @@ def print_packets_in(self): 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) + '|'; + 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) - 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) - 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]; + b = struct.unpack('B', self.ser.read())[0] # Valid Packet if b == END_BYTE: - packet_str = packet_str + '.' + "%03d" % (b) + '|VAL'; + packet_str = packet_str + '.' + "%03d" % (b) + '|VAL' print(packet_str) # logging.debug(packet_str) # Invalid Packet else: - packet_str = packet_str + '.' + "%03d" % (b) + '|INV'; + packet_str = packet_str + '.' + "%03d" % (b) + '|INV' # Reset self.attempt_reconnect = True - else: print(b) if b == END_BYTE: @@ -453,7 +462,7 @@ def check_connection(self, interval=2, max_packets_to_skip=10): 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 + # if error, attempt to reconnect self.reconnect() # check again again in 2 seconds threading.Timer(interval, self.check_connection).start() @@ -473,11 +482,11 @@ def reconnect(self): # Adds a filter at 60hz to cancel out ambient electrical noise def enable_filters(self): self.ser.write(b'f') - self.filtering_data = True; + self.filtering_data = True def disable_filters(self): self.ser.write(b'g') - self.filtering_data = False; + self.filtering_data = False def test_signal(self, signal): """ Enable / disable test signal """ @@ -500,7 +509,7 @@ def test_signal(self, signal): 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)) + 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 """ @@ -591,7 +600,7 @@ def find_port(self): openbci_serial = self.openbci_id(s) s.close() if openbci_serial: - openbci_port = port; + openbci_port = port except (OSError, serial.SerialException): pass if openbci_port == '': diff --git a/openbci/ganglion.py b/openbci/ganglion.py index 90a9695..c345fca 100644 --- a/openbci/ganglion.py +++ b/openbci/ganglion.py @@ -177,8 +177,8 @@ def init_streaming(self): 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.") + 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...") @@ -211,7 +211,8 @@ def handleDiscovery(self, dev, isNewDev, isNewData): 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) + print("Got Ganglion: " + value + + ", with MAC: " + dev.addr) break nb_ganglions = len(list_mac) @@ -294,7 +295,7 @@ def start_streaming(self, callback, lapse=-1): if (lapse > 0 and timeit.default_timer() - start_time > lapse): self.stop() if self.log: - self.log_packet_count = self.log_packet_count + 1; + self.log_packet_count = self.log_packet_count + 1 # Checking connection -- timeout and packets dropped self.check_connection() @@ -318,7 +319,8 @@ def test_signal(self, signal): 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)) + 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 """ @@ -392,13 +394,15 @@ 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.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 @@ -412,7 +416,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() @@ -421,7 +426,7 @@ def reconnect(self): class OpenBCISample(object): - """Object encapulsating a single sample from the OpenBCI board.""" + """Object encapsulating a single sample from the OpenBCI board.""" def __init__(self, packet_id, channel_data, aux_data, imp_data): self.id = packet_id @@ -444,7 +449,8 @@ def __init__(self, scaling_output=True): 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 + # 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 @@ -459,11 +465,12 @@ def handleNotification(self, cHandle, data): """ PARSER: - Parses incoming data packet into OpenBCISample -- see docs. Will call the corresponding parse* function depending on the format of the packet. + 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 + # 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)) @@ -506,7 +513,8 @@ def parse(self, packet): 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') + print('Wrong size, for raw data' + + str(len(packet)) + ' instead of 19 bytes') return chan_data = [] @@ -514,14 +522,16 @@ def parseRaw(self, packet_id, packet): 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.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') + 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 @@ -535,7 +545,8 @@ def parse19bit(self, packet_id, packet): # 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.pushSample(sample_id, full_data, + self.lastAcceleromoter, self.lastImpedance) self.lastChannelData = full_data delta_id += 1 self.updatePacketsCount(packet_id) @@ -543,7 +554,8 @@ def parse19bit(self, packet_id, packet): 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') + print('Wrong size, for 18-bit compression data' + + str(len(packet)) + ' instead of 19 bytes') return # accelerometer X @@ -566,7 +578,8 @@ def parse18bit(self, packet_id, packet): # 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.pushSample(sample_id, full_data, + self.lastAcceleromoter, self.lastImpedance) self.lastChannelData = full_data delta_id += 1 self.updatePacketsCount(packet_id) @@ -580,7 +593,8 @@ def parseImpedance(self, packet_id, packet): 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) + 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. """ @@ -637,7 +651,7 @@ def conv24bitsToInt(unpacked): 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] @@ -650,11 +664,11 @@ def conv19bitToInt32(threeByteBuffer): 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; + prefix = 0b1111111111111 return ((prefix << 19) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[ 2]) | ~0xFFFFFFFF else: @@ -664,13 +678,13 @@ def conv19bitToInt32(threeByteBuffer): 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.") + 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 = 0b11111111111111; + prefix = 0b11111111111111 return ((prefix << 18) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[ 2]) | ~0xFFFFFFFF else: @@ -758,7 +772,7 @@ def decompressDeltas19Bit(buffer): miniBuf = [(buffer[16] & 0x07), buffer[17], buffer[18]] receivedDeltas[1][3] = conv19bitToInt32(miniBuf) - return receivedDeltas; + return receivedDeltas def decompressDeltas18Bit(buffer): @@ -778,7 +792,7 @@ def decompressDeltas18Bit(buffer): ((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 = [ @@ -786,7 +800,7 @@ def decompressDeltas18Bit(buffer): (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 = [ @@ -794,7 +808,7 @@ def decompressDeltas18Bit(buffer): (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 = [ @@ -802,7 +816,7 @@ def decompressDeltas18Bit(buffer): buffer[7], buffer[8] ] - receivedDeltas[0][3] = conv18bitToInt32(miniBuf); + receivedDeltas[0][3] = conv18bitToInt32(miniBuf) # Sample 2 - Channel 1 miniBuf = [ @@ -810,7 +824,7 @@ def decompressDeltas18Bit(buffer): ((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 = [ @@ -818,7 +832,7 @@ def decompressDeltas18Bit(buffer): (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 = [ @@ -826,7 +840,7 @@ def decompressDeltas18Bit(buffer): (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 = [ @@ -834,6 +848,6 @@ def decompressDeltas18Bit(buffer): buffer[16], buffer[17] ] - receivedDeltas[1][3] = conv18bitToInt32(miniBuf); + receivedDeltas[1][3] = conv18bitToInt32(miniBuf) - return receivedDeltas; + return receivedDeltas diff --git a/openbci/plugins/csv_collect.py b/openbci/plugins/csv_collect.py index 9fddc46..3ad99f5 100755 --- a/openbci/plugins/csv_collect.py +++ b/openbci/plugins/csv_collect.py @@ -19,7 +19,7 @@ def activate(self): if 'no_time' in self.args: self.file_name = self.args[0] else: - self.file_name = self.args[0] + '_' + self.file_name; + self.file_name = self.args[0] + '_' + self.file_name if 'verbose' in self.args: self.verbose = True diff --git a/openbci/plugins/noise_test.py b/openbci/plugins/noise_test.py index 2149748..23db17e 100755 --- a/openbci/plugins/noise_test.py +++ b/openbci/plugins/noise_test.py @@ -32,5 +32,5 @@ def activate(self): 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.") + 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 151c547..5f594fe 100755 --- a/openbci/plugins/print.py +++ b/openbci/plugins/print.py @@ -11,10 +11,10 @@ def __call__(self, 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]) + 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]) + sample.id, str(sample.channel_data)[1:-1], str(sample.aux_data)[1:-1]) print("---------------------------------") print(sample_string) print("---------------------------------") diff --git a/openbci/plugins/streamer_lsl.py b/openbci/plugins/streamer_lsl.py index cdac8c6..d3caa69 100755 --- a/openbci/plugins/streamer_lsl.py +++ b/openbci/plugins/streamer_lsl.py @@ -1,15 +1,16 @@ # 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 plugin_interface as plugintypes +from pylsl import StreamInfo, StreamOutlet import sys -sys.path.append('lib') # help python find pylsl relative to this example program - -from pylsl import StreamInfo, StreamOutlet -import plugin_interface as plugintypes +# 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) +# 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): @@ -60,5 +61,7 @@ def __call__(self, sample): 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".""") + 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 5e94e37..74c953e 100755 --- a/openbci/plugins/streamer_osc.py +++ b/openbci/plugins/streamer_osc.py @@ -4,7 +4,8 @@ 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): """ @@ -48,6 +49,6 @@ def __call__(self, sample): 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')""") + \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 4c583d9..f818032 100755 --- a/openbci/plugins/streamer_tcp_server.py +++ b/openbci/plugins/streamer_tcp_server.py @@ -1,9 +1,13 @@ 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 @@ -94,9 +98,9 @@ def deactivate(self): # at this point don't bother if message not sent except: continue - sock.close(); + 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 diff --git a/openbci/plugins/udp_server.py b/openbci/plugins/udp_server.py index c0882e3..7f1cc18 100755 --- a/openbci/plugins/udp_server.py +++ b/openbci/plugins/udp_server.py @@ -76,7 +76,7 @@ def send_data(self, data): # From IPlugin: close sockets, send message to client def deactivate(self): - self.server.close(); + self.server.close() def show_help(self): print("""Optional arguments: [ip [port]] diff --git a/openbci/utils/parse.py b/openbci/utils/parse.py index 6d87473..b4fbda0 100644 --- a/openbci/utils/parse.py +++ b/openbci/utils/parse.py @@ -66,8 +66,9 @@ def get_channel_data_array(self, raw_data_to_sample): 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) + 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 @@ -76,8 +77,9 @@ def get_data_array_accel(self, raw_data_to_sample): 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) + 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): @@ -102,7 +104,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 @@ -207,7 +208,8 @@ def make_daisy_sample_object_wifi(self, lower_sample_object, upper_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 diff --git a/openbci/wifi.py b/openbci/wifi.py index cbc97c9..c0a74ba 100755 --- a/openbci/wifi.py +++ b/openbci/wifi.py @@ -98,7 +98,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): @@ -158,25 +159,29 @@ 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: 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] + 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: 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' @@ -200,7 +205,8 @@ def connect(self): 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 @@ -223,14 +229,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: @@ -249,9 +257,9 @@ def wifi_shield_found(response): if nb_wifi_shields > 1: print( - "Found " + str(nb_wifi_shields) + - ", selecting first named: " + list_id[0] + - " with IPV4: " + list_ip[0]) + "Found " + str(nb_wifi_shields) + + ", selecting first named: " + list_id[0] + + " with IPV4: " + list_ip[0]) return list_ip[0] def wifi_write(self, output): @@ -269,8 +277,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 @@ -300,7 +310,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()) @@ -463,7 +474,8 @@ def set_channel_settings(self, channel, enabled=True, gain=24, input_type=0, inc # 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)) @@ -564,13 +576,15 @@ 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 @@ -584,7 +598,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() @@ -601,10 +616,12 @@ 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) @@ -612,7 +629,8 @@ def handle_read(self): 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) + 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) diff --git a/test_log.py b/test_log.py index 59c1cdb..72ae311 100644 --- a/test_log.py +++ b/test_log.py @@ -1,9 +1,9 @@ +import time +import logging +from openbci import cyton as bci 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): diff --git a/user.py b/user.py index 1f7ed6a..881e5e3 100644 --- a/user.py +++ b/user.py @@ -1,4 +1,5 @@ #!/usr/bin/env python2.7 +from yapsy.PluginManager import PluginManager import argparse # new in Python2.7 import atexit import logging @@ -9,7 +10,6 @@ logging.basicConfig(level=logging.ERROR) -from yapsy.PluginManager import PluginManager # Load the plugins from the plugin directory. manager = PluginManager() @@ -26,7 +26,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, @@ -55,8 +56,9 @@ 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.") + 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)") @@ -92,7 +94,8 @@ 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) plugin.plugin_object.show_help() @@ -154,7 +157,8 @@ 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(), @@ -172,7 +176,6 @@ else: fun = callback_list - def cleanUp(): board.disconnect() print ("Deactivating Plugins...") @@ -180,7 +183,6 @@ def cleanUp(): plug.deactivate() print ("User.py exiting...") - atexit.register(cleanUp) print ("--------------INFO---------------") @@ -206,36 +208,37 @@ 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): + 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): + if "T:" in s: lapse = int(s[string.find(s, "T:") + 2:]) rec = True - elif ("t:" in s): + 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.") else: @@ -252,9 +255,9 @@ def cleanUp(): 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 @@ -266,11 +269,11 @@ def cleanUp(): print ("No function loaded") rec = True - elif ('test' in s): + 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 @@ -287,11 +290,12 @@ def cleanUp(): 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 + # 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 + # 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: From 81caa0ca4c2862cfb7f9e5f28f049576e7718cae Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Fri, 14 Dec 2018 13:23:33 -0600 Subject: [PATCH 4/9] More style changes Primarily shorter line lengths and convert all print statements to print functions. Also enforce print functions by importing from __future__. --- openbci/cyton.py | 37 ++++--- openbci/ganglion.py | 82 ++++++++------ openbci/plugins/csv_collect.py | 4 +- openbci/plugins/noise_test.py | 5 +- openbci/plugins/print.py | 6 +- openbci/plugins/sample_rate.py | 1 + openbci/plugins/streamer_lsl.py | 26 +++-- openbci/plugins/streamer_osc.py | 4 +- openbci/plugins/streamer_tcp_server.py | 11 +- openbci/plugins/udp_server.py | 4 +- openbci/utils/parse.py | 78 +++++++++----- openbci/utils/utilities.py | 68 +++++++----- openbci/wifi.py | 71 +++++++------ plugin_interface.py | 1 + scripts/stream_data.py | 5 +- scripts/stream_data_wifi.py | 3 +- scripts/stream_data_wifi_high_speed.py | 3 +- scripts/test.py | 1 + scripts/udp_client.py | 1 + scripts/udp_server.py | 3 +- test_log.py | 1 + tests/test_constants.py | 142 ++++++++++++------------- tests/test_parse.py | 18 ++-- user.py | 73 +++++++------ 24 files changed, 378 insertions(+), 270 deletions(-) diff --git a/openbci/cyton.py b/openbci/cyton.py index feb5b19..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 @@ -69,8 +71,8 @@ class OpenBCICyton(object): 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): + 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 @@ -162,8 +164,8 @@ def start_streaming(self, callback, lapse=-1): 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. + 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') @@ -182,17 +184,22 @@ def start_streaming(self, callback, lapse=-1): # 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 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 + # 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 + # 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, + whole_sample = OpenBCISample(sample.id, + sample.channel_data + + self.last_odd_sample.channel_data, avg_aux_data) for call in callback: call(whole_sample) @@ -200,7 +207,7 @@ def start_streaming(self, callback, lapse=-1): for call in callback: call(sample) - if (lapse > 0 and timeit.default_timer() - start_time > lapse): + if lapse > 0 and (timeit.default_timer() - start_time) > lapse: self.stop() if self.log: self.log_packet_count = self.log_packet_count + 1 @@ -263,7 +270,8 @@ def read(n): literal_read = pre_fix + literal_read - # unpack little endian(>) signed integer(i) (makes unpacking platform independent) + # unpack little endian(>) signed integer(i) + # (makes unpacking platform independent) myInt = struct.unpack('>i', literal_read)[0] if self.scaling_output: @@ -456,7 +464,8 @@ def print_packets_in(self): else: skipped_str = skipped_str + "%03d" % (b) + '.' - if self.attempt_reconnect and (timeit.default_timer() - self.last_reconnect) > self.reconnect_freq: + 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() @@ -615,7 +624,9 @@ def find_port(self): class OpenBCISample(object): - """Object encapulsating a single sample from the OpenBCI board. NB: dummy imp for plugin compatiblity""" + """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 diff --git a/openbci/ganglion.py b/openbci/ganglion.py index aaea541..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 @@ -52,7 +53,8 @@ class OpenBCIGanglion(object): 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 + 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 """ @@ -104,26 +106,26 @@ def setImpedance(self, 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.") + 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...") + print("Get mainservice...") self.service = self.gang.getServiceByUUID(BLE_SERVICE) - print ("Got:" + str(self.service)) + print("Got:" + str(self.service)) - print ("Get characteristics...") + 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())) + 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())) + 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())) + 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) @@ -162,10 +164,13 @@ def init_streaming(self): 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.""" + """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.") + "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...") @@ -176,9 +181,9 @@ def __init__(self): def handleDiscovery(self, dev, isNewDev, isNewData): if isNewDev: - print ("Discovered device: " + dev.addr) + print("Discovered device: " + dev.addr) elif isNewData: - print ("Received new data from: " + dev.addr) + print("Received new data from: " + dev.addr) scanner = Scanner().withDelegate(ScanDelegate()) devices = scanner.scan(scan_time) @@ -193,7 +198,8 @@ def handleDiscovery(self, dev, isNewDev, isNewData): list_id = [] for dev in devices: - # "Ganglion" should appear inside the "value" associated to "Complete Local Name", e.g. "Ganglion-b2a6" + # "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) @@ -251,8 +257,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. """ if not self.streaming: self.init_streaming() @@ -264,7 +270,8 @@ def start_streaming(self, callback, lapse=-1): 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 + # 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()) @@ -388,8 +395,10 @@ def warn(self, 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 @@ -466,7 +475,8 @@ def parse(self, packet): start_byte = unpac[0] - # Give the informative part of the packet to proper handler -- split between ID and data bytes + # 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 @@ -492,7 +502,7 @@ def parse(self, packet): # End of ASCII message elif start_byte == 207: print("%\t" + str(packet[1:])) - print ("$$$") + print("$$$") self.receiving_ASCII = False else: print("Warning: unknown type of packet: " + str(start_byte)) @@ -572,7 +582,9 @@ def parse18bit(self, packet_id, packet): 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""" + """ 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"') @@ -656,10 +668,11 @@ def conv19bitToInt32(threeByteBuffer): # 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 + return ((prefix << 19) | (threeByteBuffer[0] << 16) | + (threeByteBuffer[1] << 8) | threeByteBuffer[2]) | ~0xFFFFFFFF else: - return (prefix << 19) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2] + return (prefix << 19) | (threeByteBuffer[0] << 16) |\ + (threeByteBuffer[1] << 8) | threeByteBuffer[2] def conv18bitToInt32(threeByteBuffer): @@ -672,10 +685,11 @@ def conv18bitToInt32(threeByteBuffer): # 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 + return ((prefix << 18) | (threeByteBuffer[0] << 16) | + (threeByteBuffer[1] << 8) | threeByteBuffer[2]) | ~0xFFFFFFFF else: - return (prefix << 18) | (threeByteBuffer[0] << 16) | (threeByteBuffer[1] << 8) | threeByteBuffer[2] + return (prefix << 18) | (threeByteBuffer[0] << 16) |\ + (threeByteBuffer[1] << 8) | threeByteBuffer[2] def conv8bitToInt8(byte): @@ -691,7 +705,8 @@ 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.) + 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.") @@ -766,7 +781,8 @@ 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.) + 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.") diff --git a/openbci/plugins/csv_collect.py b/openbci/plugins/csv_collect.py index 3ad99f5..9829213 100755 --- a/openbci/plugins/csv_collect.py +++ b/openbci/plugins/csv_collect.py @@ -1,3 +1,4 @@ +from __future__ import print_function import csv import timeit import datetime @@ -8,7 +9,8 @@ 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.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 diff --git a/openbci/plugins/noise_test.py b/openbci/plugins/noise_test.py index 23db17e..120dc57 100755 --- a/openbci/plugins/noise_test.py +++ b/openbci/plugins/noise_test.py @@ -1,3 +1,4 @@ +from __future__ import print_function import timeit import numpy as np import plugin_interface as plugintypes @@ -14,7 +15,7 @@ def __call__(self, sample): if elapsed_time > self.polling_interval: channel_noise_power = np.divide(self.diff, self.sample_count) - print (channel_noise_power) + print(channel_noise_power) self.diff = np.zeros(self.eeg_channels) self.last_report = timeit.default_timer() @@ -31,6 +32,6 @@ def activate(self): self.polling_interval = float(self.args[0]) def show_help(self): - print ("Optional argument: polling_interval -- in seconds, default: 10. \n \ + 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 5f594fe..32c5ce0 100755 --- a/openbci/plugins/print.py +++ b/openbci/plugins/print.py @@ -1,3 +1,4 @@ +from __future__ import print_function import plugin_interface as plugintypes @@ -10,8 +11,9 @@ 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]) + 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]) diff --git a/openbci/plugins/sample_rate.py b/openbci/plugins/sample_rate.py index 012c160..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 diff --git a/openbci/plugins/streamer_lsl.py b/openbci/plugins/streamer_lsl.py index d3caa69..0c4e2dd 100755 --- a/openbci/plugins/streamer_lsl.py +++ b/openbci/plugins/streamer_lsl.py @@ -1,6 +1,7 @@ # 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) +from __future__ import print_function import plugin_interface as plugintypes from pylsl import StreamInfo, StreamOutlet import sys @@ -9,7 +10,8 @@ sys.path.append('lib') -# Use LSL protocol to broadcast data using one stream for EEG, one stream for AUX, one last for impedance testing +# 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 @@ -36,21 +38,27 @@ def activate(self): # 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); + "- 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); + "- 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); + 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 @@ -61,7 +69,7 @@ def __call__(self, sample): self.outlet_imp.push_sample(sample.imp_data) def show_help(self): - print("""Optional arguments: + 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 74c953e..828cd57 100755 --- a/openbci/plugins/streamer_osc.py +++ b/openbci/plugins/streamer_osc.py @@ -1,3 +1,4 @@ +from __future__ import print_function # requires python-osc from pythonosc import osc_message_builder from pythonosc import udp_client @@ -32,7 +33,8 @@ def activate(self): 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) + 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 diff --git a/openbci/plugins/streamer_tcp_server.py b/openbci/plugins/streamer_tcp_server.py index f818032..077504f 100755 --- a/openbci/plugins/streamer_tcp_server.py +++ b/openbci/plugins/streamer_tcp_server.py @@ -1,3 +1,4 @@ +from __future__ import print_function from threading import Thread import socket import select @@ -13,7 +14,9 @@ # 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): @@ -79,7 +82,8 @@ def initialize(self): # 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 + # 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 @@ -122,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 7f1cc18..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: @@ -32,7 +33,8 @@ # # 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("---------------------------------") diff --git a/openbci/utils/parse.py b/openbci/utils/parse.py index b4fbda0..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,20 +64,27 @@ 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]) + 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) + 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]) + 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 @@ -129,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 @@ -167,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() @@ -190,20 +205,27 @@ 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() diff --git a/openbci/utils/utilities.py b/openbci/utils/utilities.py index 0f0729c..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 @@ -26,60 +26,78 @@ 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)]) + [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)]) + [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)]) + [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)]) + [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)]) + [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)]) + [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)]) + [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)]) + [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)]) + [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)]) + [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 c0a74ba..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 @@ -32,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 @@ -50,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 """ @@ -169,14 +173,14 @@ def connect(self): (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: + 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) @@ -201,7 +205,8 @@ def connect(self): 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. """ @@ -212,11 +217,12 @@ def init_streaming(self): 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") @@ -295,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() @@ -428,12 +434,12 @@ 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 @@ -483,7 +489,7 @@ 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') elif sample_rate == 500: @@ -500,7 +506,7 @@ def set_sample_rate(self, sample_rate): 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') elif sample_rate == 400: @@ -527,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') @@ -583,8 +589,10 @@ def warn(self, 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 @@ -628,22 +636,25 @@ def handle_read(self): 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])) + 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 74c2e69..70550fe 100755 --- a/plugin_interface.py +++ b/plugin_interface.py @@ -17,6 +17,7 @@ class PluginExample(plugintypes.IPluginExtended): ... """ +from __future__ import print_function from yapsy.IPlugin import IPlugin diff --git a/scripts/stream_data.py b/scripts/stream_data.py index fc1bd66..afb6a74 100644 --- a/scripts/stream_data.py +++ b/scripts/stream_data.py @@ -1,4 +1,5 @@ -import sys; +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 @@ -95,7 +96,7 @@ 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 diff --git a/scripts/stream_data_wifi.py b/scripts/stream_data_wifi.py index 9acd91e..de30a17 100644 --- a/scripts/stream_data_wifi.py +++ b/scripts/stream_data_wifi.py @@ -1,4 +1,5 @@ -import sys; +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 diff --git a/scripts/stream_data_wifi_high_speed.py b/scripts/stream_data_wifi_high_speed.py index 8f2b3ec..c9dd654 100644 --- a/scripts/stream_data_wifi_high_speed.py +++ b/scripts/stream_data_wifi_high_speed.py @@ -1,4 +1,5 @@ -import sys; +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 diff --git a/scripts/test.py b/scripts/test.py index 4871b92..c5e4312 100644 --- a/scripts/test.py +++ b/scripts/test.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys sys.path.append('..') # help python find cyton.py relative to scripts folder diff --git a/scripts/udp_client.py b/scripts/udp_client.py index 9dd37dc..b5d717f 100644 --- a/scripts/udp_client.py +++ b/scripts/udp_client.py @@ -1,5 +1,6 @@ """A sample client for the OpenBCI UDP server.""" +from __future__ import print_function import argparse try: diff --git a/scripts/udp_server.py b/scripts/udp_server.py index 1f01f04..277f132 100644 --- a/scripts/udp_server.py +++ b/scripts/udp_server.py @@ -7,6 +7,7 @@ - websockets """ +from __future__ import print_function import argparse try: @@ -14,7 +15,7 @@ except ImportError: import _pickle as pickle import json -import sys; +import sys sys.path.append('..') # help python find cyton.py relative to scripts folder from openbci import cyton as open_bci diff --git a/test_log.py b/test_log.py index 72ae311..aeb05d7 100644 --- a/test_log.py +++ b/test_log.py @@ -1,3 +1,4 @@ +from __future__ import print_function import time import logging from openbci import cyton as bci diff --git a/tests/test_constants.py b/tests/test_constants.py index 27f3157..5db62b9 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -1,99 +1,99 @@ 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__': 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 881e5e3..89b6b21 100644 --- a/user.py +++ b/user.py @@ -1,4 +1,5 @@ #!/usr/bin/env python2.7 +from __future__ import print_function from yapsy.PluginManager import PluginManager import argparse # new in Python2.7 import atexit @@ -16,7 +17,7 @@ if __name__ == '__main__': - print ("------------user.py-------------") + print("------------user.py-------------") parser = argparse.ArgumentParser(description="OpenBCI 'user'") parser.add_argument('--board', default="cyton", help="Choose between [cyton] and [ganglion] boards.") @@ -56,15 +57,15 @@ 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.") + 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)") from openbci import 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) @@ -84,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... @@ -94,28 +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 + + 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)) + 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, @@ -132,17 +133,17 @@ # 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 @@ -157,17 +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...") + 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("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) @@ -178,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\ @@ -213,14 +213,13 @@ def cleanUp(): if not s: pass elif "help" in s: - print ("View command map at: \ + 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 @@ -240,7 +239,7 @@ def cleanUp(): 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): @@ -252,7 +251,7 @@ def cleanUp(): except: raise else: - print ("No function loaded") + print("No function loaded") rec = True elif "start" in s: @@ -266,7 +265,7 @@ def cleanUp(): except: raise else: - print ("No function loaded") + print("No function loaded") rec = True elif 'test' in s: From 7700bfd247cb7010c55615eed471980588b13836 Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Fri, 11 Jan 2019 09:14:43 -0700 Subject: [PATCH 5/9] Check for platform before importing Ganglion --- openbci/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 From c6529a6637ea8b440d6219190971f021addb1cd0 Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Fri, 11 Jan 2019 09:14:58 -0700 Subject: [PATCH 6/9] Remove bluepy from requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) 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 From 955202dc753187ecbac2940b3e34ab274a2247c7 Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Fri, 11 Jan 2019 09:29:21 -0700 Subject: [PATCH 7/9] Add bluepy install to travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index cd882db..80c390d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,8 @@ 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 From a477d2549bcda852d216bff2ded638e7e62bdf0b Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Fri, 11 Jan 2019 09:43:07 -0700 Subject: [PATCH 8/9] Clarify the use of BluePy in README --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e2b7741..3ea67ff 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'` From 39aee8ce4d2a138dffb1404b8c039e513257168b Mon Sep 17 00:00:00 2001 From: Ian McKenzie Date: Fri, 11 Jan 2019 09:47:56 -0700 Subject: [PATCH 9/9] misc README fixes --- README.md | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 3ea67ff..e53ddbc 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,6 @@ Warning: Connecting pins to high frequency 2x amp signal --> a Corresponding SD file OBCI_18.TXT$$$ --> /start T:3 - ``` NOTES: @@ -300,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 ```