diff --git a/.gitignore b/.gitignore index f681089a3..3dfa38f5e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,6 @@ OpenBCI_GUI/SavedData/EEG_Data/SDconverted-* OpenBCI_GUI/data/EEG_Data/* OpenBCI_GUI/data/OpenBCIHub/* OpenBCI_GUI/application.* -OpenBCI_GUI/openbcigui_* +openbcigui_* *.autosave .vscode/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..e17e27bab --- /dev/null +++ b/.travis.yml @@ -0,0 +1,51 @@ +os: + - linux + - osx + +addons: + artifacts: + paths: + - $(ls $TRAVIS_BUILD_DIR/openbcigui_*_macosx.dmg | tr "\n" ":") + - $(ls $TRAVIS_BUILD_DIR/openbcigui_*_linux64.zip | tr "\n" ":") + target_paths: + - /${TRAVIS_BRANCH}/${TRAVIS_COMMIT} + - /${TRAVIS_BRANCH}/latest + working_dir: $TRAVIS_BUILD_DIR + +before_install: +- if [ "$TRAVIS_OS_NAME" = osx ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then openssl aes-256-cbc -K $encrypted_2f5d2771e3cb_key -iv $encrypted_2f5d2771e3cb_iv -in release_script/mac_only/Certificates.p12.enc -out release_script/mac_only/Certificates.p12 -d; fi +- if [ "$TRAVIS_OS_NAME" = osx ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then chmod +x release_script/mac_only/add-osx-cert.sh; fi +- if [ "$TRAVIS_OS_NAME" = osx ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then ./release_script/mac_only/add-osx-cert.sh; fi + +install: + - mkdir $TRAVIS_BUILD_DIR/temp; cd $TRAVIS_BUILD_DIR/temp + +### ### LINUX ### ### + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -O -L http://download.processing.org/processing-3.5.3-linux64.tgz ;fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then tar -xzvf processing-3.5.3-linux64.tgz ;fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then export PATH=$TRAVIS_BUILD_DIR/temp/processing-3.5.3:$PATH ;fi + # copy libraries to linux location + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then mkdir -p $HOME/sketchbook/libraries/ ;fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then cp -a $TRAVIS_BUILD_DIR/OpenBCI_GUI/libraries/. $HOME/sketchbook/libraries/ ;fi + +### ### MAC ### ### + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then curl -O -L http://download.processing.org/processing-3.5.3-macosx.zip ;fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then unzip processing-3.5.3-macosx.zip ;fi + # Processing.app must be in this location for processing-java to work + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then mv Processing.app /Applications/Processing.app ;fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then export PATH=$TRAVIS_BUILD_DIR/release_script/mac_only:$PATH ;fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then chmod +x $TRAVIS_BUILD_DIR/release_script/mac_only/processing-java ;fi + # copy libraries to mac location + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then mkdir -p $HOME/Documents/Processing/libraries/ ;fi + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then cp -a $TRAVIS_BUILD_DIR/OpenBCI_GUI/libraries/. $HOME/Documents/Processing/libraries/ ;fi + # used to create .dmg + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then pip install dmgbuild ;fi + +script: + - cd $TRAVIS_BUILD_DIR + - python $TRAVIS_BUILD_DIR/release_script/make-release.py --no-prompts + +notifications: + email: + on_success: never + on_failure: always \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a9493c74d..f89af22bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,41 @@ +# v5.0.0 + +### Improvements +* Use BrainFlow Java Binding to handle data acquisition (no need to run the Hub!) +* Speed up entire GUI by plotting data more efficiently +* Updated OpenBCI Data Format (CSV) Files, with more detailed information and data +* Popup with link to GUI v4 file coverter script +* Improved Playback Mode and Time Series +* Refactored GUI data flow +* Add Travis and Appveyor CI tests and builds for all OS +* Add data smoothing option for live Cyton data +* Cyton Port manual selection only displays serial ports with a dongle connected. +* Cyton SD file read works without conversion to playback file +* Use BrainFlow filters, add 1-100 BandPass filter +* Can Hide/Show channels in time series + +### Bug Fixes +* Remove OpenBCI Hub #665 #669 #708 +* General UI/UX improvements +* Missing Filter Button Label in Networking #696 +* Ganglion+WiFi Accelerometer Data not in Sync #512 +* Remove # Chan Textfield from LSL in Networking Widget #644 +* LSL manual timestamping interferes with LSL clock_offset correction #775 +* Fixed a graphics related error on linux #816 + +### Deprecated Features +* OpenBCI Hub - This is no longer required to run the GUI! +* Old OBCI (CSV) Files - A converter will be made available +* Presentation Mode +* SSVEP_Beta Widget +* Focus Widget +* Marker Mode Widget +* OpenBionics Widget + # v4.2.0 Please use OpenBCIHub v2.1.0 and Processing 4. -# Improvements +### Improvements * Update to Processing 4 and Java 11! #671 * Add functional Spectrogram Widget! #416 * Clean up Marker Mode UDP listener #305 diff --git a/Networking-Test-Kit/LSL/lslStreamTest.py b/Networking-Test-Kit/LSL/lslStreamTest.py index 05b8bb9f6..dc5bc876d 100644 --- a/Networking-Test-Kit/LSL/lslStreamTest.py +++ b/Networking-Test-Kit/LSL/lslStreamTest.py @@ -1,5 +1,5 @@ """Example program to show how to read a multi-channel time series from LSL.""" - +import time from pylsl import StreamInlet, resolve_stream # first resolve an EEG stream on the lab network @@ -9,8 +9,18 @@ # create a new inlet to read from the stream inlet = StreamInlet(streams[0]) -while True: + +def testLSLSamplingRate(): + start = time.time() + numSamples = 0 + while time.time() < start + 5: # get a new sample (you can also omit the timestamp part if you're not # interested in it) - sample, timestamp = inlet.pull_sample() - print(timestamp, sample) \ No newline at end of file + sample, timestamp = inlet.pull_chunk() + # print(timestamp, sample) + if timestamp: + numSamples += 1 + print( numSamples / 5 ) + + +testLSLSamplingRate() \ No newline at end of file diff --git a/Networking-Test-Kit/LSL/lslStreamTest_3Streams.py b/Networking-Test-Kit/LSL/lslStreamTest_3Streams.py index 3d7dbde70..bca94ade1 100644 --- a/Networking-Test-Kit/LSL/lslStreamTest_3Streams.py +++ b/Networking-Test-Kit/LSL/lslStreamTest_3Streams.py @@ -7,32 +7,46 @@ """ from pylsl import StreamInlet, resolve_stream +import time numStreams = 3 # first resolve an EEG stream on the lab network print("looking for an EEG stream...") -stream1 = resolve_stream('name', 'obci_eeg1') -stream2 = resolve_stream('name', 'obci_eeg2') -stream3 = resolve_stream('name', 'obci_eeg3') +stream1 = resolve_stream('type', 'EEG') +stream2 = resolve_stream('type', 'AUX') +stream3 = resolve_stream('type', 'FFT') # create a new inlet to read from the stream inlet = StreamInlet(stream1[0]) inlet2 = StreamInlet(stream2[0]) inlet3 = StreamInlet(stream3[0]) -while True: - for i in range(numStreams): - # get a new sample (you can also omit the timestamp part if you're not - # interested in it) - if i == 0: - chunk, timestamps = inlet.pull_chunk() - if timestamps: - print("Stream", i + 1, " == ", chunk) - elif i == 1: - chunk, timestamps = inlet2.pull_chunk() - if timestamps: - print("Stream", i + 1, " == ", chunk) - elif i == 2: - chunk, timestamps = inlet3.pull_chunk() - if timestamps: - print("Stream", i + 1, " == ", chunk) \ No newline at end of file +def testLSLSamplingRates(): + print( "Testing Sampling Rates..." ) + start = time.time() + numSamples1 = 0 + numSamples2 = 0 + numSamples3 = 0 + while time.time() < start + 5: + # get a new sample (you can also omit the timestamp part if you're not + # interested in it) + for i in range(numStreams): + if i == 0: + chunk, timestamps = inlet.pull_chunk() + if timestamps: + numSamples1 += 1 + elif i == 1: + chunk, timestamps2 = inlet2.pull_chunk() + if timestamps2: + numSamples2 += 1 + elif i == 2: + chunk, timestamps3 = inlet3.pull_chunk() + if timestamps3: + numSamples3 += 1 + #print("Stream", i + 1, " == ", chunk) + print( "Stream 1 Sampling Rate == ", numSamples1, " | Type : EEG") + print( "Stream 2 Sampling Rate == ", numSamples2, " | Type : AUX") + print( "Stream 3 Sampling Rate == ", numSamples3, " | Type : FFT") + + +testLSLSamplingRates() \ No newline at end of file diff --git a/Networking-Test-Kit/Serial/SerialPortListTest.pde b/Networking-Test-Kit/Serial/SerialPortListTest.pde new file mode 100644 index 000000000..4b9b00534 --- /dev/null +++ b/Networking-Test-Kit/Serial/SerialPortListTest.pde @@ -0,0 +1,11 @@ +import com.fazecast.jSerialComm.*; + +println("GETTING A LIST OF SERIAL PORTS: "); + +printArray(SerialPort.getCommPorts()); + +SerialPort comPort = SerialPort.getCommPorts()[1]; + +println(comPort.getSystemPortName()); + +println("Done :)"); \ No newline at end of file diff --git a/Networking-Test-Kit/Serial/USB4JavaTest.pde b/Networking-Test-Kit/Serial/USB4JavaTest.pde new file mode 100644 index 000000000..422859d4a --- /dev/null +++ b/Networking-Test-Kit/Serial/USB4JavaTest.pde @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 Klaus Reimer + * See LICENSE.txt for licensing information. + */ +import java.nio.ByteBuffer; +import org.usb4java.Context; +import org.usb4java.Device; +import org.usb4java.DeviceDescriptor; +import org.usb4java.DeviceList; +import org.usb4java.LibUsb; +import org.usb4java.LibUsbException; +import org.usb4java.BufferUtils; + +// This example will fetch Vendor ID and Product ID for all USB ports +// However, this doesn't help identify COM port names across OS + +/** + * Simply lists all available USB devices. + * + * @author Klaus Reimer + */ + +// Create the libusb context +Context context = new Context(); + +// Initialize the libusb context +int result = LibUsb.init(context); +if (result < 0) +{ + throw new LibUsbException("Unable to initialize libusb", result); +} + +// Read the USB device list +DeviceList list = new DeviceList(); +result = LibUsb.getDeviceList(context, list); +if (result < 0) +{ + throw new LibUsbException("Unable to get device list", result); +} + +try +{ + // Iterate over all devices and list them + for (Device device: list) + { + int address = LibUsb.getDeviceAddress(device); + int busNumber = LibUsb.getBusNumber(device); + DeviceDescriptor descriptor = new DeviceDescriptor(); + result = LibUsb.getDeviceDescriptor(device, descriptor); + if (result < 0) + { + throw new LibUsbException( + "Unable to read device descriptor", result); + } + System.out.format( + "Bus %03d, Device %03d: Vendor %04x, Product %04x%n", + busNumber, address, descriptor.idVendor(), + descriptor.idProduct()); + + println(descriptor.dump()); + + ByteBuffer path = BufferUtils.allocateByteBuffer(8); + result = LibUsb.getPortNumbers(device, path); + if (result > 0) + { + for (int i = 0; i < result; i++) + { + System.out.print(path.get(i)); + if (i + 1 < result) System.out.print("-"); + } + System.out.println(""); + } + + } + + +} +finally +{ + // Ensure the allocated device list is freed + LibUsb.freeDeviceList(list, true); +} + +// Deinitialize the libusb context +LibUsb.exit(context); \ No newline at end of file diff --git a/OpenBCI_GUI/ADS1299SettingsBoard.pde b/OpenBCI_GUI/ADS1299SettingsBoard.pde new file mode 100644 index 000000000..a828fb66d --- /dev/null +++ b/OpenBCI_GUI/ADS1299SettingsBoard.pde @@ -0,0 +1,247 @@ + +interface ADSSettingsEnum { + public String getName(); + public ADSSettingsEnum getNext(); +} + +enum PowerDown implements ADSSettingsEnum { + ON("Active"), + OFF("Inactive"); + + private String name; + + PowerDown(String _name) { + this.name = _name; + } + + @Override + public String getName() { + return name; + } + + @Override + public PowerDown getNext() { + PowerDown[] vals = values(); + return vals[(this.ordinal()+1) % vals.length]; + } +} + +// the scalar values are actually used to scale eeg data +enum Gain implements ADSSettingsEnum { + X1("x1", 1.0), + X2("x2", 2.0), + X4("x4", 4.0), + X6("x6", 6.0), + X8("x8", 8.0), + X12("x12", 12.0), + X24("x24", 24.0); + + private String name; + private double scalar; + + Gain(String _name, double _scalar) { + this.name = _name; + this.scalar = _scalar; + + } + + @Override + public String getName() { + return name; + } + + @Override + public Gain getNext() { + Gain[] vals = values(); + return vals[(this.ordinal()+1) % vals.length]; + } + + public double getScalar() { + return scalar; + } +} + +enum InputType implements ADSSettingsEnum { + NORMAL("Normal"), + SHORTED("Shorted"), + BIAS_MEAS("Bias Meas"), + MVDD("MVDD"), + TEMP("Temp"), + TEST("Test"), + BIAS_DRP("BIAS DRP"), + BIAS_DRN("BIAS DRN"); + + private String name; + + InputType(String _name) { + this.name = _name; + } + + @Override + public String getName() { + return name; + } + + @Override + public InputType getNext() { + InputType[] vals = values(); + return vals[(this.ordinal()+1) % vals.length]; + } +} + +enum Bias implements ADSSettingsEnum { + NO_INCLUDE("Don't Include"), + INCLUDE("Include"); + + private String name; + + Bias(String _name) { + this.name = _name; + } + + @Override + public String getName() { + return name; + } + + @Override + public Bias getNext() { + Bias[] vals = values(); + return vals[(this.ordinal()+1) % vals.length]; + } +} + +enum Srb2 implements ADSSettingsEnum { + DISCONNECT("Off"), + CONNECT("On"); + + private String name; + + Srb2(String _name) { + this.name = _name; + } + + @Override + public String getName() { + return name; + } + + @Override + public Srb2 getNext() { + Srb2[] vals = values(); + return vals[(this.ordinal()+1) % vals.length]; + } +} + +enum Srb1 implements ADSSettingsEnum { + DISCONNECT("Off"), + CONNECT("On"); + + private String name; + + Srb1(String _name) { + this.name = _name; + } + + @Override + public String getName() { + return name; + } + + @Override + public Srb1 getNext() { + Srb1[] vals = values(); + return vals[(this.ordinal()+1) % vals.length]; + } +} + +class ADS1299Settings { + protected PowerDown[] powerDown; + protected Gain[] gain; + protected InputType[] inputType; + protected Bias[] bias; + protected Srb2[] srb2; + protected Srb1[] srb1; + + protected Board board; + protected ADS1299SettingsBoard settingsBoard; + + private Bias[] previousBias; + private Srb2[] previousSrb2; + + ADS1299Settings(Board theBoard) { + board = theBoard; + settingsBoard = (ADS1299SettingsBoard)theBoard; + + int channelCount = board.getNumEXGChannels(); + + // initialize all arrays with some defaults + // (which happen to be Cyton defaults, but they don't have to be. + // we set defaults on board contruction) + powerDown = new PowerDown[channelCount]; + Arrays.fill(powerDown, PowerDown.ON); + + gain = new Gain[channelCount]; + Arrays.fill(gain, Gain.X24); + + inputType = new InputType[channelCount]; + Arrays.fill(inputType, InputType.NORMAL); + + bias = new Bias[channelCount]; + Arrays.fill(bias, Bias.INCLUDE); + + srb2 = new Srb2[channelCount]; + Arrays.fill(srb2, Srb2.CONNECT); + + srb1 = new Srb1[channelCount]; + Arrays.fill(srb1, Srb1.DISCONNECT); + + previousBias = bias.clone(); + previousSrb2 = srb2.clone(); + } + + public boolean isChannelActive(int chan) { + return powerDown[chan] == PowerDown.ON; + } + + public void setChannelActive(int chan, boolean active) { + if (active) { + bias[chan] = previousBias[chan]; + srb2[chan] = previousSrb2[chan]; + + } else { + previousBias[chan] = bias[chan]; + previousSrb2[chan] = srb2[chan]; + + bias[chan] = Bias.NO_INCLUDE; + srb2[chan] = Srb2.DISCONNECT; + } + + powerDown[chan] = active ? PowerDown.ON : PowerDown.OFF; + commit(chan); + } + + public void commit(int chan) { + String command = String.format("x%c%d%d%d%d%d%dX", settingsBoard.getChannelSelector(chan), + powerDown[chan].ordinal(), gain[chan].ordinal(), + inputType[chan].ordinal(), bias[chan].ordinal(), + srb2[chan].ordinal(), srb1[chan].ordinal()); + + board.sendCommand(command); + } + + public void commitAll() { + for (int i=0; i activeChannels; + + ADS1299SettingsController(List _activeChannels, int _x, int _y, int _w, int _h, int _channelBarHeight){ + x = _x; + y = _y; + w = _w; + h = _h; + + activeChannels = _activeChannels; + ADS1299SettingsBoard settingsBoard = (ADS1299SettingsBoard)currentBoard; + boardSettings = settingsBoard.getADS1299Settings(); + createAllButtons(_channelBarHeight); + } + + public void update(){ + for (int i=0; i accumulatedData = new FixedStack(); + private double[][] dataThisFrame; + + // accessible by all boards, can be returned as valid empty data + protected double[][] emptyData; + + @Override + public boolean initialize() { + boolean res = initializeInternal(); + + double[] fillData = new double[getTotalChannelCount()]; + accumulatedData.setSize(getCurrentBoardBufferSize()); + accumulatedData.fill(fillData); + + emptyData = new double[getTotalChannelCount()][0]; + + return res; + } + + @Override + public void uninitialize() { + uninitializeInternal(); + } + + @Override + public void update() { + updateInternal(); + + dataThisFrame = getNewDataInternal(); + + for (int i = 0; i < dataThisFrame[0].length; i++) { + double[] newEntry = new double[getTotalChannelCount()]; + for (int j = 0; j < getTotalChannelCount(); j++) { + newEntry[j] = dataThisFrame[j][i]; + } + + accumulatedData.push(newEntry); + } + } + + @Override + public int getNumEXGChannels() { + return getEXGChannels().length; + } + + // returns all the data this board has received in this frame + @Override + public double[][] getFrameData() { + return dataThisFrame; + } + + @Override + public List getData(int maxSamples) { + int endIndex = accumulatedData.size(); + int startIndex = max(0, endIndex - maxSamples); + + return accumulatedData.subList(startIndex, endIndex); + } + + public String[] getChannelNames() { + String[] names = new String[getTotalChannelCount()]; + Arrays.fill(names, "Other"); + + names[getTimestampChannel()] = "Timestamp"; + names[getSampleNumberChannel()] = "Sample Index"; + + int[] exgChannels = getEXGChannels(); + for (int i=0; i set = new TreeSet(); + // maybe it will be nice to add method like get_exg_channels to brainflow to avoid this ugly code? + // but I doubt that smth else will need it and in python I know how to implement it better using existing API + try { + channels = BoardShim.get_eeg_channels(getBoardIdInt()); + for(int i = 0; i < channels.length; i++) { + set.add(channels[i]); + } + } catch (BrainFlowError e) { + println("WARNING: failed to get eeg channels from BoardShim"); + } + try { + channels = BoardShim.get_emg_channels(getBoardIdInt()); + for(int i = 0; i < channels.length; i++) { + set.add(channels[i]); + } + } catch (BrainFlowError e) { + println("WARNING: failed to get emg channels from BoardShim"); + } + try { + channels = BoardShim.get_ecg_channels(getBoardIdInt()); + for(int i = 0; i < channels.length; i++) { + set.add(channels[i]); + } + } catch (BrainFlowError e) { + println("WARNING: failed to get ecg channels from BoardShim"); + } + try { + channels = BoardShim.get_eog_channels(getBoardIdInt()); + for(int i = 0; i < channels.length; i++) { + set.add(channels[i]); + } + } catch (BrainFlowError e) { + println("WARNING: failed to get eog channels from BoardShim"); + } + Integer[] toArray = set.toArray(new Integer[set.size()]); + exgChannelsCache = new int[toArray.length]; + for (int i = 0; i < toArray.length; i++) { + exgChannelsCache[i] = toArray[i].intValue(); + } + } + + return exgChannelsCache; + } + + @Override + public int getTimestampChannel() { + if(timeStampChannelCache < 0) { + try { + timeStampChannelCache = BoardShim.get_timestamp_channel(getBoardIdInt()); + } catch (BrainFlowError e) { + println("WARNING: failed to get timestamp channel from BoardShim"); + e.printStackTrace(); + } + } + + return timeStampChannelCache; + } + + @Override + public int getSampleNumberChannel() { + if(packetNumberChannelCache < 0) { + try { + packetNumberChannelCache = BoardShim.get_package_num_channel(getBoardIdInt()); + } catch (BrainFlowError e) { + println("WARNING: failed to get package num channel from BoardShim"); + e.printStackTrace(); + } + } + + return packetNumberChannelCache; + } + + public int getBoardIdInt() { + return getBoardId().get_code(); + } + + @Override + public void sendCommand(String command) { + if (command != null && isConnected()) { + try { + println("Sending config string to board: " + command); + boardShim.config_board(command); + } + catch (BrainFlowError e) { + println("ERROR: Exception sending config string to board: " + command); + e.printStackTrace(); + } + } + } + + @Override + protected double[][] getNewDataInternal() { + if(streaming) { + try { + return boardShim.get_board_data(); + } catch (BrainFlowError e) { + println("WARNING: could not get board data."); + e.printStackTrace(); + } + } + + return emptyData; + } + + @Override + public int getTotalChannelCount() { + if(totalChannelsCache < 0) { + try { + totalChannelsCache = BoardShim.get_num_rows(getBoardIdInt()); + } catch (BrainFlowError e) { + println("WARNING: failed to get num rows from BoardShim"); + e.printStackTrace(); + } + } + + return totalChannelsCache; + } + + protected int[] getOtherChannels() { + if (otherChannelsCache == null) { + try { + otherChannelsCache = BoardShim.get_other_channels(getBoardIdInt()); + } catch (BrainFlowError e) { + e.printStackTrace(); + } + } + + return otherChannelsCache; + } +}; diff --git a/OpenBCI_GUI/BoardCyton.pde b/OpenBCI_GUI/BoardCyton.pde index ea007fa5a..e0094b7b9 100644 --- a/OpenBCI_GUI/BoardCyton.pde +++ b/OpenBCI_GUI/BoardCyton.pde @@ -1,35 +1,7 @@ -/////////////////////////////////////////////////////////////////////////////// -// -// This class configures and manages the connection to the OpenBCI shield for -// the Arduino. The connection is implemented via a Serial connection. -// The OpenBCI is configured using single letter text commands sent from the -// PC to the Arduino. The EEG data streams back from the Arduino to the PC -// continuously (once started). This class defaults to using binary transfer -// for normal operation. -// -// Created: Chip Audette, Oct 2013 -// Modified: through April 2014 -// Modified again: Conor Russomanno Sept-Oct 2014 -// Modified for Daisy (16-chan) OpenBCI V3: Conor Russomanno Nov 2014 -// Modified Daisy Behaviors: Chip Audette Dec 2014 -// -// Note: this class now expects the data format produced by OpenBCI V3. -// -///////////////////////////////////////////////////////////////////////////// - - -//------------------------------------------------------------------------ -// Global Variables & Instances -//------------------------------------------------------------------------ - -final char command_stop = 's'; -// final String command_startText = "x"; -final char command_startBinary = 'b'; - -final char[] command_deactivate_channel = {'1', '2', '3', '4', '5', '6', '7', '8', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i'}; -final char[] command_activate_channel = {'!', '@', '#', '$', '%', '^', '&', '*', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I'}; - -enum BoardMode { +import brainflow.*; + + +enum CytonBoardMode { DEFAULT(0), DEBUG(1), ANALOG(2), @@ -37,493 +9,468 @@ enum BoardMode { MARKER(4); private final int value; - BoardMode(final int newValue) { + CytonBoardMode(final int newValue) { value = newValue; } public int getValue() { return value; } } -//------------------------------------------------------------------------ -// Classes -//------------------------------------------------------------------------ - -class Cyton { - - private int nEEGValuesPerPacket = 8; //defined by the data format sent by cyton boards - private int nAuxValuesPerPacket = 3; //defined by the data format sent by cyton boards - private DataPacket_ADS1299 rawReceivedDataPacket; - private DataPacket_ADS1299 missedDataPacket; - private DataPacket_ADS1299 dataPacket; - - private final int fsHzSerialCyton = 250; //sample rate used by OpenBCI board...set by its Arduino code - private final int fsHzSerialCytonDaisy = 125; //sample rate used by OpenBCI board...set by its Arduino code - private final int fsHzWifi = 1000; //sample rate used by OpenBCI board...set by its Arduino code - private final float ADS1299_Vref = 4.5f; //reference voltage for ADC in ADS1299. set by its hardware - private float ADS1299_gain = 24.0; //assumed gain setting for ADS1299. set by its Arduino code - private float openBCI_series_resistor_ohms = 2200; // Ohms. There is a series resistor on the 32 bit board. - private float scale_fac_uVolts_per_count = ADS1299_Vref / ((float)(pow(2, 23)-1)) / ADS1299_gain * 1000000.f; //ADS1299 datasheet Table 7, confirmed through experiment - private final float scale_fac_accel_G_per_count = 0.002 / ((float)pow(2, 4)); //assume set to +/4G, so 2 mG per digit (datasheet). Account for 4 bits unused - //private final float scale_fac_accel_G_per_count = 1.0; //to test stimulations //final float scale_fac_accel_G_per_count = 1.0; - private final float leadOffDrive_amps = 6.0e-9; //6 nA, set by its Arduino code - - private BoardMode curBoardMode = BoardMode.DEFAULT; - - private int curInterface = INTERFACE_SERIAL; - private int sampleRate = fsHzWifi; - - // needed by interfaceserial - public int hardwareSyncStep = 0; //start this at 0... - public String potentialFailureMessage = ""; - public String defaultChannelSettings = ""; - public String daisyOrNot = ""; - - // used to detect and flag error during initialization - public boolean daisyNotAttached = false; - - //some get methods - public float getSampleRate() { - if (isSerial()) { - if (nchan == NCHAN_CYTON_DAISY) { - return fsHzSerialCytonDaisy; - } else { - return fsHzSerialCyton; - } - } else { - return hub.getSampleRate(); - } - } +public enum CytonSDMode { + NO_WRITE("Do not write to SD...", null), + MAX_5MIN("5 minute maximum", "A"), + MAX_15MIN("15 minute maximum", "S"), + MAX_30MIN("30 minute maximum", "F"), + MAX_1HR("1 hour maximum", "G"), + MAX_2HR("2 hour maximum", "H"), + MAX_4HR("4 hour maximum", "J"), + MAX_12HR("12 hour maximum", "K"), + MAX_24HR("24 hour maximum", "L"); + + private String name; + private String command; - public BoardMode getBoardMode() { - return curBoardMode; + CytonSDMode(String _name, String _command) { + this.name = _name; + this.command = _command; } - public int getInterface() { - return curInterface; + + public String getName() { + return name; } - public float get_series_resistor() { - return openBCI_series_resistor_ohms; + + public String getCommand() { + return command; } - public float get_scale_fac_uVolts_per_count() { - return scale_fac_uVolts_per_count; +} + +static class BoardCytonConstants { + static final float series_resistor_ohms = 2200; // Ohms. There is a series resistor on the 32 bit board. + static final float ADS1299_Vref = 4.5f; //reference voltage for ADC in ADS1299. set by its hardware + static final float ADS1299_gain = 24.f; //assumed gain setting for ADS1299. set by its Arduino code + static final float scale_fac_uVolts_per_count = ADS1299_Vref / ((float)(pow(2, 23)-1)) / ADS1299_gain * 1000000.f; //ADS1299 datasheet Table 7, confirmed through experiment + static final float leadOffDrive_amps = 6.0e-9; //6 nA, set by its Arduino code + static final float accelScale = 0.002 / (pow (2, 4)); +} + +class BoardCytonSerial extends BoardCytonSerialBase { + public BoardCytonSerial() { + super(); } - public float get_scale_fac_accel_G_per_count() { - return scale_fac_accel_G_per_count; + + public BoardCytonSerial(String serialPort) { + super(); + this.serialPort = serialPort; } - public float get_leadOffDrive_amps() { - return leadOffDrive_amps; + + @Override + public BoardIds getBoardId() { + return BoardIds.CYTON_BOARD; } +}; - public void setBoardMode(BoardMode boardMode) { - hub.sendCommand("/" + boardMode.getValue()); - curBoardMode = boardMode; - println("Cyton: setBoardMode to :" + curBoardMode); +class BoardCytonSerialDaisy extends BoardCytonSerialBase { + public BoardCytonSerialDaisy() { + super(); } - public void setSampleRate(int _sampleRate) { - sampleRate = _sampleRate; - // output("Setting sample rate for Cyton to " + sampleRate + "Hz"); - println("Setting sample rate for Cyton to " + sampleRate + "Hz"); - hub.setSampleRate(sampleRate); + public BoardCytonSerialDaisy(String serialPort) { + super(); + this.serialPort = serialPort; } - public boolean setInterface(int _interface) { - curInterface = _interface; - // println("current interface: " + curInterface); - println("setInterface: curInterface: " + getInterface()); - if (isWifi()) { - setSampleRate((int)fsHzWifi); - hub.setProtocol(PROTOCOL_WIFI); - } else if (isSerial()) { - setSampleRate((int)fsHzSerialCyton); - hub.setProtocol(PROTOCOL_SERIAL); - } - return true; + @Override + public BoardIds getBoardId() { + return BoardIds.CYTON_DAISY_BOARD; } +}; - //constructors - Cyton() {}; //only use this if you simply want access to some of the constants - Cyton(PApplet applet, String comPort, int baud, int nEEGValuesPerOpenBCI, boolean useAux, int nAuxValuesPerOpenBCI, int _interface) { - curInterface = _interface; +abstract class BoardCytonSerialBase extends BoardCyton implements SmoothingCapableBoard{ - initDataPackets(nEEGValuesPerOpenBCI, nAuxValuesPerOpenBCI); + private Buffer buffer = null; + private volatile boolean smoothData; - if (isSerial()) { - hub.connectSerial(comPort); - } else if (isWifi()) { - hub.connectWifi(comPort); + public BoardCytonSerialBase() { + super(); + smoothData = false; + } + + // synchronized is important to ensure that we dont free buffers during getting data + @Override + public synchronized void setSmoothingActive(boolean active) { + if (smoothData == active) { + return; + } + // dont touch accumulatedData buffer to dont pause streaming + if (active) { + buffer = new Buffer(getSampleRate()); + } else { + buffer = null; } + smoothData = active; } - public void initDataPackets(int _nEEGValuesPerPacket, int _nAuxValuesPerPacket) { - nEEGValuesPerPacket = _nEEGValuesPerPacket; - nAuxValuesPerPacket = _nAuxValuesPerPacket; - //allocate space for data packet - rawReceivedDataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels - missedDataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels - dataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this could be 8 or 16 channels - //set all values to 0 so not null + @Override + public boolean getSmoothingActive() { + return smoothData; + } - for (int i = 0; i < nEEGValuesPerPacket; i++) { - rawReceivedDataPacket.values[i] = 0; - //prevDataPacket.values[i] = 0; + @Override + protected synchronized double[][] getNewDataInternal() { + double[][] data = super.getNewDataInternal(); + if (!smoothData) { + return data; } - - for (int i=0; i < nEEGValuesPerPacket; i++) { - dataPacket.values[i] = 0; - missedDataPacket.values[i] = 0; + // transpose to push to buffer + for (int i = 0; i < data[0].length; i++) { + double[] newEntry = new double[getTotalChannelCount()]; + for (int j = 0; j < getTotalChannelCount(); j++) { + newEntry[j] = data[j][i]; + } + buffer.addNewEntry(newEntry); } - for (int i = 0; i < nAuxValuesPerPacket; i++) { - rawReceivedDataPacket.auxValues[i] = 0; - dataPacket.auxValues[i] = 0; - missedDataPacket.auxValues[i] = 0; - //prevDataPacket.auxValues[i] = 0; + int numData = buffer.getDataCount(); + if (numData == 0) { + return emptyData; } + // transpose back + double[][] res = new double[getTotalChannelCount()][numData]; + for (int i = 0; i < numData; i++) { + double[] curData = buffer.popFirstEntry(); + for (int j = 0; j < getTotalChannelCount(); j++) { + res[j][i] = curData[j]; + } + } + return res; } - public int closeSDandPort() { - closeSDFile(); - return closePort(); +}; + +class BoardCytonWifi extends BoardCytonWifiBase { + public BoardCytonWifi() { + super(); + } + public BoardCytonWifi(String ipAddress, int samplingRate) { + super(samplingRate); + this.ipAddress = ipAddress; } - public int closePort() { - if (isSerial()) { - return hub.disconnectSerial(); - } else { - return hub.disconnectWifi(); - } + @Override + public BoardIds getBoardId() { + return BoardIds.CYTON_WIFI_BOARD; } +}; - public int closeSDFile() { - println("Closing any open SD file. Writing 'j' to OpenBCI."); - if (isPortOpen()) write('j'); // tell the SD file to close if one is open... - delay(100); //make sure 'j' gets sent to the board - return 0; +class BoardCytonWifiDaisy extends BoardCytonWifiBase { + public BoardCytonWifiDaisy() { + super(); + } + public BoardCytonWifiDaisy(String ipAddress, int samplingRate) { + super(samplingRate); + this.ipAddress = ipAddress; } - public void syncWithHardware(int sdSetting) { - switch (hardwareSyncStep) { - case 1: //send # of channels (8 or 16) ... (regular or daisy setup) - println("Cyton: syncWithHardware: [1] Sending channel count (" + nchan + ") to OpenBCI..."); - if (nchan == 8) { - write('c'); - } - if (nchan == 16) { - write('C', false); - } - break; - case 2: //reset hardware to default registers - println("Cyton: syncWithHardware: [2] Reseting OpenBCI registers to default... writing \'d\'..."); - write('d'); // TODO: Why does this not get a $$$ readyToSend = false? - break; - case 3: //ask for series of channel setting ASCII values to sync with channel setting interface in GUI - println("Cyton: syncWithHardware: [3] Retrieving OpenBCI's channel settings to sync with GUI... writing \'D\'... waiting for $$$..."); - write('D', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 4: //check existing registers - println("Cyton: syncWithHardware: [4] Retrieving OpenBCI's full register map for verification... writing \'?\'... waiting for $$$..."); - write('?', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 5: - // write("j"); // send OpenBCI's 'j' commaned to make sure any already open SD file is closed before opening another one... - switch (sdSetting) { - case 1: //"5 min max" - write('A', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 2: //"15 min max" - write('S', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 3: //"30 min max" - write('F', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 4: //"1 hr max" - write('G', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 5: //"2 hr max" - write('H', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 6: //"4 hr max" - write('J', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 7: //"12 hr max" - write('K', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - case 8: //"24 hr max" - write('L', false); //wait for $$$ to iterate... applies to commands expecting a response - break; - default: - break; // Do Nothing - } - println("Cyton: syncWithHardware: [5] Writing selected SD setting (" + sdSettingString + ") to OpenBCI..."); - //final hacky way of abandoning initiation if someone selected daisy but doesn't have one connected. - if(abandonInit){ - haltSystem(); - output("No daisy board present. Make sure you selected the correct number of channels."); - controlPanel.open(); - abandonInit = false; - } - break; - case 6: - println("Cyton: syncWithHardware: The GUI is done initializing. Click outside of the control panel to interact with the GUI."); - hub.changeState(HubState.STOPPED); - systemMode = 10; - controlPanel.close(); - topNav.controlPanelCollapser.setIsActive(false); - //renitialize GUI if nchan has been updated... needs to be built - break; - } + @Override + public BoardIds getBoardId() { + return BoardIds.CYTON_DAISY_WIFI_BOARD; + } +}; + +abstract class BoardCytonWifiBase extends BoardCyton { + // https://docs.openbci.com/docs/02Cyton/CytonSDK#sample-rate + private Map samplingRateCommands = new HashMap() {{ + put(16000, "~0"); + put(8000, "~1"); + put(4000, "~2"); + put(2000, "~3"); + put(1000, "~4"); + put(500, "~5"); + put(250, "~6"); + }}; + + public BoardCytonWifiBase() { + super(); + } + + public BoardCytonWifiBase(int samplingRate) { + super(); + samplingRateCache = samplingRate; } - public boolean write(char val) { - if (hub.isHubRunning()) { - hub.sendCommand(val); - return true; + @Override + public boolean initializeInternal() { + boolean res = super.initializeInternal(); + + if ((res) && (samplingRateCache > 0)){ + String command = samplingRateCommands.get(samplingRateCache); + sendCommand(command); } - return false; + return res; } +}; - public boolean write(char val, boolean _readyToSend) { - return write(val); +class CytonDefaultSettings extends ADS1299Settings { + CytonDefaultSettings(Board theBoard) { + super(theBoard); + + // the 'd' command is automatically sent by brainflow on prepare_session + Arrays.fill(powerDown, PowerDown.ON); + Arrays.fill(gain, Gain.X24); + Arrays.fill(inputType, InputType.NORMAL); + Arrays.fill(bias, Bias.INCLUDE); + Arrays.fill(srb2, Srb2.CONNECT); + Arrays.fill(srb1, Srb1.DISCONNECT); } +} + +abstract class BoardCyton extends BoardBrainFlow +implements ImpedanceSettingsBoard, AccelerometerCapableBoard, AnalogCapableBoard, DigitalCapableBoard, ADS1299SettingsBoard { + private final char[] channelSelectForSettings = {'1', '2', '3', '4', '5', '6', '7', '8', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I'}; + + private ADS1299Settings currentADS1299Settings; + private boolean[] isCheckingImpedance; + + // same for all channels + private final double brainflowGain = 24.0; + + private int[] accelChannelsCache = null; + private int[] analogChannelsCache = null; + + protected String serialPort = ""; + protected String ipAddress = ""; + private CytonBoardMode currentBoardMode = CytonBoardMode.DEFAULT; - private boolean isSerial () { - // println("My interface is " + curInterface); - return curInterface == INTERFACE_SERIAL; + public BoardCyton() { + super(); + + isCheckingImpedance = new boolean[getNumEXGChannels()]; + Arrays.fill(isCheckingImpedance, false); + + // The command 'd' is automatically sent by brainflow on prepare_session + currentADS1299Settings = new CytonDefaultSettings(this); } - private boolean isWifi () { - return curInterface == INTERFACE_HUB_WIFI; + // implement mandatory abstract functions + @Override + protected BrainFlowInputParams getParams() { + BrainFlowInputParams params = new BrainFlowInputParams(); + params.serial_port = serialPort; + params.ip_address = ipAddress; + params.ip_port = 6677; + return params; } - public void startDataTransfer() { - if (isPortOpen()) { - // Now give the command to start binary data transmission - if (isSerial()) { - hub.changeState(HubState.NORMAL); // make sure it's now interpretting as binary - println("Cyton: startDataTransfer(): writing \'" + command_startBinary + "\' to the serial port..."); - write(command_startBinary); - } else if (isWifi()) { - println("Cyton: startDataTransfer(): writing \'" + command_startBinary + "\' to the wifi shield..."); - write(command_startBinary); - } + @Override + public boolean initializeInternal() { + return super.initializeInternal(); + } - } else { - println("Cyton: Port not open"); - } + @Override + public void uninitializeInternal() { + closeSDFile(); + super.uninitializeInternal(); } - public void stopDataTransfer() { - if (isPortOpen()) { - hub.changeState(HubState.STOPPED); // make sure it's now interpretting as binary - println("Cyton: startDataTransfer(): writing \'" + command_stop + "\' to the serial port..."); - write(command_stop);// + "\n"); + @Override + public void setEXGChannelActive(int channelIndex, boolean active) { + currentADS1299Settings.setChannelActive(channelIndex, active); + } + + @Override + public boolean isEXGChannelActive(int channelIndex) { + return currentADS1299Settings.isChannelActive(channelIndex); + } + + @Override + public boolean isAccelerometerActive() { + return getBoardMode() == CytonBoardMode.DEFAULT; + } + + @Override + public void setAccelerometerActive(boolean active) { + if(active) { + setBoardMode(CytonBoardMode.DEFAULT); } + // no way of turning off accel. } - public void printRegisters() { - if (isPortOpen()) { - println("Cyton: printRegisters(): Writing ? to OpenBCI..."); - write('?'); + @Override + public boolean canDeactivateAccelerometer() { + return false; + } + + @Override + public int[] getAccelerometerChannels() { + if (accelChannelsCache == null) { + try { + accelChannelsCache = BoardShim.get_accel_channels(getBoardIdInt()); + } catch (BrainFlowError e) { + e.printStackTrace(); + } } + + return accelChannelsCache; } - private boolean isPortOpen() { - if (isWifi() || isSerial()) { - return hub.isPortOpen(); - } else { - return false; + @Override + public boolean isAnalogActive() { + return getBoardMode() == CytonBoardMode.ANALOG; + } + + @Override + public void setAnalogActive(boolean active) { + if(active) { + setBoardMode(CytonBoardMode.ANALOG); } } - //activate or deactivate an EEG channel...channel counting is zero through nchan-1 - public void changeChannelState(int Ichan, boolean activate) { - if (isPortOpen()) { - // if ((Ichan >= 0) && (Ichan < command_activate_channel.length)) { - if ((Ichan >= 0)) { - if (activate) { - // write(command_activate_channel[Ichan]); - // gui.cc.powerUpChannel(Ichan); - w_timeSeries.hsc.powerUpChannel(Ichan); - } else { - // write(command_deactivate_channel[Ichan]); - // gui.cc.powerDownChannel(Ichan); - w_timeSeries.hsc.powerDownChannel(Ichan); - } + @Override + public boolean canDeactivateAnalog() { + return false; + } + + @Override + public int[] getAnalogChannels() { + if (analogChannelsCache == null) { + try { + analogChannelsCache = BoardShim.get_analog_channels(getBoardIdInt()); + } catch (BrainFlowError e) { + e.printStackTrace(); } } + + return analogChannelsCache; } - //deactivate an EEG channel...channel counting is zero through nchan-1 - public void deactivateChannel(int Ichan) { - if (isPortOpen()) { - if ((Ichan >= 0) && (Ichan < command_deactivate_channel.length)) { - write(command_deactivate_channel[Ichan]); - } + @Override + public boolean isDigitalActive() { + return getBoardMode() == CytonBoardMode.DIGITAL; + } + + @Override + public void setDigitalActive(boolean active) { + if(active) { + setBoardMode(CytonBoardMode.DIGITAL); } } - //activate an EEG channel...channel counting is zero through nchan-1 - public void activateChannel(int Ichan) { - if (isPortOpen()) { - if ((Ichan >= 0) && (Ichan < command_activate_channel.length)) { - write(command_activate_channel[Ichan]); + @Override + public boolean canDeactivateDigital() { + return false; + } + + @Override + public int[] getDigitalChannels() { + // the removeAll function will remove array indices 0 and 5. + // remove other_channel[0] because it's the end byte + // remove other_channels[5] because it does not contain digital data + int[] digitalChannels = ArrayUtils.removeAll(getOtherChannels(), 0, 5); // remove non-digital channels + return digitalChannels; + } + + @Override + public void setCheckingImpedance(int channel, boolean active) { + char p = '0'; + char n = '0'; + + if (active) { + Srb2 srb2sSetting = currentADS1299Settings.srb2[channel]; + if (srb2sSetting == Srb2.CONNECT) { + n = '1'; + } + else { + p = '1'; } } + + // for example: z 4 1 0 Z + String command = String.format("z%c%c%cZ", channelSelectForSettings[channel], p, n); + sendCommand(command); + + isCheckingImpedance[channel] = active; + } + + @Override + public boolean isCheckingImpedance(int channel) { + return isCheckingImpedance[channel]; } - public void configureAllChannelsToDefault() { - write('d'); - }; - - /** - * Used to convert a gain from the hub back into local codes. - */ - public char getCommandForGain(int gain) { - switch (gain) { - case 1: - return '0'; - case 2: - return '1'; - case 4: - return '2'; - case 6: - return '3'; - case 8: - return '4'; - case 12: - return '5'; - case 24: - default: - return '6'; + @Override + protected double[][] getNewDataInternal() { + double[][] data = super.getNewDataInternal(); + int[] exgChannels = getEXGChannels(); + for (int i = 0; i < exgChannels.length; i++) { + for (int j = 0; j < data[exgChannels[i]].length; j++) { + // brainflow assumes a fixed gain of 24. Undo brainflow's scaling and apply new scale. + double scalar = brainflowGain / currentADS1299Settings.gain[i].getScalar(); + data[exgChannels[i]][j] *= scalar; + } } + return data; + } + + @Override + public ADS1299Settings getADS1299Settings() { + return currentADS1299Settings; } - /** - * Used to convert raw code to hub code - * @param inputType {String} - The input from a hub sync channel with register settings - */ - public char getCommandForInputType(String inputType) { - if (inputType.equals("normal")) return '0'; - if (inputType.equals("shorted")) return '1'; - if (inputType.equals("biasMethod")) return '2'; - if (inputType.equals("mvdd")) return '3'; - if (inputType.equals("temp")) return '4'; - if (inputType.equals("testsig")) return '5'; - if (inputType.equals("biasDrp")) return '6'; - if (inputType.equals("biasDrn")) return '7'; - return '0'; - } - - /** - * Used to convert a local channel code into a hub gain which is human - * readable and in scientific values. - */ - public int getGainForCommand(char cmd) { - switch (cmd) { - case '0': - return 1; - case '1': - return 2; - case '2': - return 4; - case '3': - return 6; - case '4': - return 8; - case '5': - return 12; - case '6': - default: - return 24; + @Override + public char getChannelSelector(int channel) { + return channelSelectForSettings[channel]; + } + + public CytonBoardMode getBoardMode() { + return currentBoardMode; + } + + private void setBoardMode(CytonBoardMode boardMode) { + sendCommand("/" + boardMode.getValue()); + currentBoardMode = boardMode; + } + + @Override + public void startStreaming() { + openSDFile(); + super.startStreaming(); + } + + @Override + public void stopStreaming() { + closeSDFile(); + super.stopStreaming(); + } + + public void openSDFile() { + //If selected, send command to Cyton to enabled SD file recording for selected duration + if (cyton_sdSetting != CytonSDMode.NO_WRITE) { + println("Opening SD file. Writing " + cyton_sdSetting.getCommand() + " to Cyton."); + sendCommand(cyton_sdSetting.getCommand()); } } - /** - * Used right before a channel setting command is sent to the hub to convert - * local values into the expected form for the hub. - */ - public String getInputTypeForCommand(char cmd) { - final String inputTypeShorted = "shorted"; - final String inputTypeBiasMethod = "biasMethod"; - final String inputTypeMvdd = "mvdd"; - final String inputTypeTemp = "temp"; - final String inputTypeTestsig = "testsig"; - final String inputTypeBiasDrp = "biasDrp"; - final String inputTypeBiasDrn = "biasDrn"; - final String inputTypeNormal = "normal"; - switch (cmd) { - case '1': - return inputTypeShorted; - case '2': - return inputTypeBiasMethod; - case '3': - return inputTypeMvdd; - case '4': - return inputTypeTemp; - case '5': - return inputTypeTestsig; - case '6': - return inputTypeBiasDrp; - case '7': - return inputTypeBiasDrn; - case '0': - default: - return inputTypeNormal; + public void closeSDFile() { + if (cyton_sdSetting != CytonSDMode.NO_WRITE) { + println("Closing any open SD file. Writing 'j' to Cyton."); + sendCommand("j"); // tell the SD file to close if one is open... } } - /** - * Used to convert a local index number to a hub human readable sd setting - * command. - */ - public String getSDSettingForSetting(int setting) { - switch (setting) { - case 1: - return "5min"; - case 2: - return "15min"; - case 3: - return "30min"; - case 4: - return "1hour"; - case 5: - return "2hour"; - case 6: - return "4hour"; - case 7: - return "12hour"; - case 8: - return "24hour"; - default: - return ""; + public void printRegisters() { + println("Cyton: printRegisters(): Writing ? to OpenBCI..."); + sendCommand("?"); + } + + @Override + protected void addChannelNamesInternal(String[] channelNames) { + for (int i=0; i samplingRateCommands = new HashMap() {{ + put(25600, "~0"); + put(12800, "~1"); + put(6400, "~2"); + put(3200, "~3"); + put(1600, "~4"); + put(800, "~5"); + put(400, "~6"); + put(200, "~7"); + }}; + + public BoardGanglionWifi(String ipAddress, int samplingRate) { + super(); + this.ipAddress = ipAddress; + samplingRateCache = samplingRate; + } + + @Override + public boolean initializeInternal() + { + // turn on accel by default, or is it handled somewhere else? + boolean res = super.initializeInternal(); + + if ((res) && (samplingRateCache > 0)){ + String command = samplingRateCommands.get(samplingRateCache); + sendCommand(command); + } - private int curInterface = INTERFACE_NONE; + return res; + } - private DataPacket_ADS1299 dataPacket; + @Override + public BoardIds getBoardId() { + return BoardIds.GANGLION_WIFI_BOARD; + } +}; - private boolean checkingImpedance = false; - private boolean accelModeActive = false; +abstract class BoardGanglion extends BoardBrainFlow implements AccelerometerCapableBoard { - public int[] impedanceArray = new int[NCHAN_GANGLION + 1]; + private final char[] deactivateChannelChars = {'1', '2', '3', '4', '5', '6', '7', '8', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i'}; + private final char[] activateChannelChars = {'!', '@', '#', '$', '%', '^', '&', '*', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I'}; + + private int[] accelChannelsCache = null; + private int[] resistanceChannelsCache = null; - private int sampleRate = (int)fsHzWifi; + private boolean[] exgChannelActive; - // Getters - public float getSampleRate() { - if (isBLE()) { - return fsHzBLE; - } else { - return hub.getSampleRate(); - } - } + protected String serialPort = ""; + protected String macAddress = ""; + protected String ipAddress = ""; + private boolean isCheckingImpedance = false; + private boolean isGettingAccel = false; - public float get_scale_fac_uVolts_per_count() { - return scale_fac_uVolts_per_count; + // implement mandatory abstract functions + @Override + protected BrainFlowInputParams getParams() { + BrainFlowInputParams params = new BrainFlowInputParams(); + params.serial_port = serialPort; + params.mac_address = macAddress; + params.ip_address = ipAddress; + params.ip_port = 6677; + return params; } - public float get_scale_fac_accel_G_per_count() { - if (isWifi()) { - return scale_fac_accel_G_per_count_wifi; - } else { - return scale_fac_accel_G_per_count_ble; - } - } - public boolean isCheckingImpedance() { return checkingImpedance; } - public boolean isAccelModeActive() { - return isWifi() ? true : accelModeActive; //Accel is always on for Ganglion+Wifi + @Override + public void setEXGChannelActive(int channelIndex, boolean active) { + char[] charsToUse = active ? activateChannelChars : deactivateChannelChars; + sendCommand(str(charsToUse[channelIndex])); + exgChannelActive[channelIndex] = active; } - public void overrideCheckingImpedance(boolean val) { checkingImpedance = val; } - public int getInterface() { - return curInterface; - } - public boolean isBLE () { - return curInterface == INTERFACE_HUB_BLE || curInterface == INTERFACE_HUB_BLED112; + + @Override + public boolean isEXGChannelActive(int channelIndex) { + return exgChannelActive[channelIndex]; } - public boolean isWifi () { - return curInterface == INTERFACE_HUB_WIFI; - } + @Override + public boolean initializeInternal() + { + // turn on accel by default, or is it handled somewhere else? + boolean res = super.initializeInternal(); + + setAccelerometerActive(true); + exgChannelActive = new boolean[getNumEXGChannels()]; + Arrays.fill(exgChannelActive, true); - public boolean isPortOpen() { - return hub.isPortOpen(); + return res; } - private PApplet mainApplet; - - //constructors - Ganglion() {}; //only use this if you simply want access to some of the constants - Ganglion(PApplet applet) { - mainApplet = applet; + @Override + public boolean isAccelerometerActive() { + return isGettingAccel; + } - initDataPackets(nEEGValuesPerPacket, nAuxValuesPerPacket); + @Override + public void setAccelerometerActive(boolean active) { + sendCommand(active ? "n" : "N"); + isGettingAccel = active; } - public void initDataPackets(int _nEEGValuesPerPacket, int _nAuxValuesPerPacket) { - nEEGValuesPerPacket = _nEEGValuesPerPacket; - nAuxValuesPerPacket = _nAuxValuesPerPacket; - // For storing data into - dataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels - for(int i = 0; i < nEEGValuesPerPacket; i++) { - dataPacket.values[i] = 0; - } - for(int i = 0; i < nAuxValuesPerPacket; i++){ - dataPacket.auxValues[i] = 0; - } + @Override + public boolean canDeactivateAccelerometer() { + return true; } - public void processImpedance(JSONObject json) { - int code = json.getInt(TCP_JSON_KEY_CODE); - if (code == RESP_SUCCESS_DATA_IMPEDANCE) { - int channel = json.getInt(TCP_JSON_KEY_CHANNEL_NUMBER); - if (channel < 5) { - int value = json.getInt(TCP_JSON_KEY_IMPEDANCE_VALUE); - impedanceArray[channel] = value; + @Override + public int[] getAccelerometerChannels() { + if (accelChannelsCache == null) { + try { + accelChannelsCache = BoardShim.get_accel_channels(getBoardIdInt()); + } catch (BrainFlowError e) { + e.printStackTrace(); } } + + return accelChannelsCache; } - public void setSampleRate(int _sampleRate) { - sampleRate = _sampleRate; - hub.setSampleRate(sampleRate); - println("Setting sample rate for Ganglion to " + sampleRate + "Hz"); - } - - public void setInterface(int _interface) { - curInterface = _interface; - if (isBLE()) { - setSampleRate((int)fsHzBLE); - if (_interface == INTERFACE_HUB_BLE) { - hub.setProtocol(PROTOCOL_BLE); - } else { - hub.setProtocol(PROTOCOL_BLED112); + public int[] getResistanceChannels() { + if (resistanceChannelsCache == null) { + try { + resistanceChannelsCache = BoardShim.get_resistance_channels(getBoardIdInt()); + } catch (BrainFlowError e) { + e.printStackTrace(); } - // hub.searchDeviceStart(); - } else if (isWifi()) { - setSampleRate((int)fsHzWifi); - hub.setProtocol(PROTOCOL_WIFI); - hub.searchDeviceStart(); } - } - // SCANNING/SEARCHING FOR DEVICES - public int closePort() { - if (isBLE()) { - hub.disconnectBLE(); - } else if (isWifi()) { - hub.disconnectWifi(); - } - return 0; + return resistanceChannelsCache; } - /** - * @description Sends a start streaming command to the Ganglion Node module. - */ - void startDataTransfer(){ - hub.changeState(HubState.NORMAL); // make sure it's now interpretting as binary - println("Ganglion: startDataTransfer(): sending \'" + command_startBinary); - if (checkingImpedance) { - impedanceStop(); - delay(100); - hub.sendCommand('b'); - } else { - hub.sendCommand('b'); + public void setCheckingImpedance(boolean checkImpedance) { + if (checkImpedance) { + if (isCheckingImpedance) { + println("Already checking impedance."); + return; + } + if (streaming) { + stopRunning(); + } + sendCommand("z"); + startStreaming(); } - } - - /** - * @description Sends a stop streaming command to the Ganglion Node module. - */ - public void stopDataTransfer() { - hub.changeState(HubState.STOPPED); // make sure it's now interpretting as binary - println("Ganglion: stopDataTransfer(): sending \'" + command_stop); - hub.sendCommand('s'); - } - - // Channel setting - //activate or deactivate an EEG channel...channel counting is zero through nchan-1 - public void changeChannelState(int Ichan, boolean activate) { - if (isPortOpen()) { - if ((Ichan >= 0)) { - if (activate) { - println("Ganglion: changeChannelState(): activate: sending " + command_activate_channel[Ichan]); - hub.sendCommand(command_activate_channel[Ichan]); - w_timeSeries.hsc.powerUpChannel(Ichan); - } else { - println("Ganglion: changeChannelState(): deactivate: sending " + command_deactivate_channel[Ichan]); - hub.sendCommand(command_deactivate_channel[Ichan]); - w_timeSeries.hsc.powerDownChannel(Ichan); - } + else { + if (!isCheckingImpedance) { + println ("Impedance is not running."); + return; } + if (streaming) { + stopStreaming(); + } + sendCommand("Z"); } + isCheckingImpedance = checkImpedance; } - - /** - * Used to start accel data mode. Accel arrays will arrive asynchronously! - */ - public void accelStart() { - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, TCP_ACTION_START); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_ACCEL); - hub.writeJSON(json); - println("Ganglion: accel: START"); - accelModeActive = true; - } - - /** - * Used to stop accel data mode. Some accel arrays may arrive after stop command - * was sent by this function. - */ - public void accelStop() { - println("Ganglion: accel: STOP"); - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, TCP_ACTION_STOP); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_ACCEL); - hub.writeJSON(json); - accelModeActive = false; - } - - /** - * Used to start impedance testing. Impedances will arrive asynchronously! - */ - public void impedanceStart() { - println("Ganglion: impedance: START"); - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, TCP_ACTION_START); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_IMPEDANCE); - hub.writeJSON(json); - checkingImpedance = true; - } - - /** - * Used to stop impedance testing. Some impedances may arrive after stop command - * was sent by this function. - */ - public void impedanceStop() { - println("Ganglion: impedance: STOP"); - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, TCP_ACTION_STOP); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_IMPEDANCE); - hub.writeJSON(json); - checkingImpedance = false; + + public boolean isCheckingImpedance() { + return isCheckingImpedance; } - - /** - * Puts the ganglion in bootloader mode. - */ - public void enterBootloaderMode() { - println("Ganglion: Entering Bootloader Mode"); - hub.sendCommand(GANGLION_BOOTLOADER_MODE); - delay(500); - closePort(); - haltSystem(); - initSystemButton.setString("START SESSION"); - controlPanel.open(); - output("Ganglion now in bootloader mode! Enjoy!"); + + @Override + protected void addChannelNamesInternal(String[] channelNames) { + for (int i=0; i extends LinkedList { + + private int samplingRate; + private int maxSize; + private Long timeOfLastCallMS; + + Buffer(int samplingRate, int maxSize) { + this.samplingRate = samplingRate; + this.maxSize = maxSize; + timeOfLastCallMS = null; + } + + Buffer(int samplingRate) { + // max delay 1 second + this(samplingRate, samplingRate /*max size*/); + } + + public void addNewEntry(T object) { + this.add(object); + } + + public T popFirstEntry() { + return this.poll(); + } + + public int getDataCount() { + long currentTime = millis(); + int numSamples = 0; + // skip first call to set time + if (timeOfLastCallMS != null) { + float deltaTimeSeconds = (currentTime - timeOfLastCallMS.longValue()) / 1000.0; + // for safety, err on the side of delivering more samples (hence the use of ceil()) + numSamples = ceil(samplingRate * deltaTimeSeconds); + } + timeOfLastCallMS = currentTime; + // ensure that buffer is not bigger than maxSize + if (this.size() > maxSize) { + numSamples += this.size() - maxSize; + } + return Math.min(numSamples, this.size()); + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/ConsoleLog.pde b/OpenBCI_GUI/ConsoleLog.pde index 3fb0d4e2b..edbc536a1 100644 --- a/OpenBCI_GUI/ConsoleLog.pde +++ b/OpenBCI_GUI/ConsoleLog.pde @@ -14,7 +14,7 @@ import java.awt.datatransfer.*; import java.awt.Toolkit; import java.awt.Desktop; -static class ConsoleWindow extends PApplet { +static class ConsoleWindow extends PApplet implements Runnable { private static ConsoleWindow instance = null; PApplet logApplet; @@ -35,14 +35,19 @@ static class ConsoleWindow extends PApplet { private int widthOfLastScreen = defaultWidth; private int heightOfLastScreen = defaultHeight; - public static void display() { - // enforce only one Console Window + static void display() { // enforce only one Console Window if (instance == null) { instance = new ConsoleWindow(); - PApplet.runSketch(new String[] {instance.getClass().getSimpleName()}, instance); + Thread t = new Thread(instance); + t.start(); } } + @Override + public void run() { + PApplet.runSketch(new String[] {instance.getClass().getSimpleName()}, instance); + } + private ConsoleWindow() { super(); } @@ -303,7 +308,7 @@ class CustomOutputStream extends PrintStream { private StringList data; private PrintWriter fileOutput; private Textarea textArea; - private final String filePath = settings.consoleDataPath+"Console_"+getDateString()+".txt"; + private final String filePath = directoryManager.getConsoleDataPath()+"Console_"+directoryManager.getFileNameDateTime()+".txt"; public CustomOutputStream(OutputStream out) { super(out); @@ -371,4 +376,4 @@ class CustomOutputStream extends PrintStream { public String getFullLog() { return join(data.array(), ""); } -} +} \ No newline at end of file diff --git a/OpenBCI_GUI/Containers.pde b/OpenBCI_GUI/Containers.pde index e80d0f8fd..15e419703 100644 --- a/OpenBCI_GUI/Containers.pde +++ b/OpenBCI_GUI/Containers.pde @@ -78,7 +78,6 @@ void drawContainers() { //alternative component listener function (line 177 - 187 frame.addComponentListener) for processing 3, if (widthOfLastScreen_C != width || heightOfLastScreen_C != height) { - println("OpenBCI_GUI: setup: RESIZED"); setupContainers(); //setupVizs(); //container extension example (more below) settings.widthOfLastScreen = width; diff --git a/OpenBCI_GUI/ControlPanel.pde b/OpenBCI_GUI/ControlPanel.pde index 16c790fdf..e5824b368 100644 --- a/OpenBCI_GUI/ControlPanel.pde +++ b/OpenBCI_GUI/ControlPanel.pde @@ -16,6 +16,15 @@ import controlP5.*; +import openbci_gui_helpers.*; + +import java.io.IOException; +import java.util.List; + +import openbci_gui_helpers.GanglionError; +import com.vmichalak.protocol.ssdp.Device; +import com.vmichalak.protocol.ssdp.SSDPClient; + //------------------------------------------------------------------------ // Global Variables & Instances //------------------------------------------------------------------------ @@ -46,16 +55,10 @@ CallbackListener cb = new CallbackListener() { //used by ControlP5 to clear text MenuList sourceList; //Global buttons and elements for the control panel (changed within the classes below) -MenuList serialList; -String[] serialPorts = new String[Serial.list().length]; - MenuList bleList; MenuList wifiList; - MenuList sdTimes; - MenuList channelList; - MenuList pollList; color boxColor = color(200); @@ -63,74 +66,51 @@ color boxStrokeColor = color(bgColor); color isSelected_color = color(184, 220, 105); color colorNotPressed = color(255); -Button noHubShowDoc; - -Button refreshPort; -Button refreshBLE; -Button refreshWifi; -Button protocolSerialCyton; -Button protocolWifiCyton; -Button protocolWifiGanglion; -Button protocolBLED112Ganglion; -Button protocolBLEGanglion; - -Button initSystemButton; -Button autoSessionName; // Reuse these buttons for Cyton and Ganglion -Button outputBDF; -Button outputODF; - -Button sampleDataButton; // Used to easily find GUI sample data for Playback mode #645 - -Button chanButton8; -Button chanButton16; -Button selectPlaybackFile; -Button selectSDFile; -Button popOutRadioConfigButton; -Button popOutWifiConfigButton; - -//Radio Button Definitions -Button getChannel; -Button setChannel; -Button ovrChannel; -Button autoscan; -Button systemStatus; - -Button eraseCredentials; -Button getIpAddress; -Button getFirmwareVersion; -Button getMacAddress; -Button getTypeOfAttachedBoard; -Button sampleRate200; //Ganglion -Button sampleRate250; -Button sampleRate500; -Button sampleRate1000; -Button sampleRate1600; //Ganglion -Button latencyCyton5ms; -Button latencyCyton10ms; -Button latencyCyton20ms; -Button latencyGanglion5ms; -Button latencyGanglion10ms; -Button latencyGanglion20ms; -Button wifiInternetProtocolCytonTCP; -Button wifiInternetProtocolCytonUDP; -Button wifiInternetProtocolCytonUDPBurst; -Button wifiInternetProtocolGanglionTCP; -Button wifiInternetProtocolGanglionUDP; -Button wifiInternetProtocolGanglionUDPBurst; -Button wifiIPAddressDynamic; -Button wifiIPAddressStatic; - -Button synthChanButton4; -Button synthChanButton8; -Button synthChanButton16; - -Serial board; +Button_obci refreshPort; +Button_obci refreshBLE; +Button_obci refreshWifi; +Button_obci protocolSerialCyton; +Button_obci protocolWifiCyton; +Button_obci protocolWifiGanglion; +Button_obci protocolBLED112Ganglion; + +Button_obci initSystemButton; +Button_obci autoSessionName; // Reuse these buttons for Cyton and Ganglion +Button_obci outputBDF; +Button_obci outputODF; + +Button_obci sampleDataButton; // Used to easily find GUI sample data for Playback mode #645 + +Button_obci chanButton8; +Button_obci chanButton16; +Button_obci selectPlaybackFile; +Button_obci popOutRadioConfigButton; + +//Radio Button_obci Definitions +Button_obci getChannel; +Button_obci setChannel; +Button_obci ovrChannel; +Button_obci autoscan; +Button_obci systemStatus; + +Button_obci sampleRate200; //Ganglion +Button_obci sampleRate250; //Cyton +Button_obci sampleRate500; //Cyton +Button_obci sampleRate1000; //Cyton +Button_obci sampleRate1600; //Ganglion +Button_obci wifiIPAddressDynamic; +Button_obci wifiIPAddressStatic; + +Button_obci synthChanButton4; +Button_obci synthChanButton8; +Button_obci synthChanButton16; ChannelPopup channelPopup; PollPopup pollPopup; RadioConfigBox rcBox; -WifiConfigBox wcBox; +Map BLEMACAddrMap = new HashMap(); +int selectedSamplingRate = -1; //------------------------------------------------------------------------ // Global Functions @@ -143,73 +123,46 @@ public void controlEvent(ControlEvent theEvent) { controlPanel.hideAllBoxes(); Map bob = ((MenuList)theEvent.getController()).getItem(int(theEvent.getValue())); - String str = (String)bob.get("headline"); + String str = (String)bob.get("headline"); // Get the text displayed in the MenuList + int newDataSource = (int)bob.get("value"); settings.controlEventDataSource = str; //Used for output message on system start - int newDataSource = int(theEvent.getValue()); - - eegDataSource = newDataSource; // reset global eegDataSource to the selected value from the list + eegDataSource = newDataSource; - if (newDataSource != DATASOURCE_SYNTHETIC && newDataSource != DATASOURCE_PLAYBACKFILE && !hub.isHubRunning()) { - outputError("Unable to establish link to Hub. LIVE functionality will be disabled."); - println("ControlEvent: Hub error"); - return; - } - - // this button only used on mac - if(isMac()) { - protocolBLEGanglion.setColorNotPressed(colorNotPressed); - } protocolWifiGanglion.setColorNotPressed(colorNotPressed); protocolBLED112Ganglion.setColorNotPressed(colorNotPressed); protocolWifiCyton.setColorNotPressed(colorNotPressed); protocolSerialCyton.setColorNotPressed(colorNotPressed); - ganglion.setInterface(INTERFACE_NONE); - cyton.setInterface(INTERFACE_NONE); + //Reset protocol + selectedProtocol = BoardProtocol.NONE; - if(newDataSource == DATASOURCE_CYTON){ + //Perform this check in a way that ignores order of items in the menulist + if (eegDataSource == DATASOURCE_CYTON) { updateToNChan(8); chanButton8.setColorNotPressed(isSelected_color); chanButton16.setColorNotPressed(colorNotPressed); //default color of button - latencyCyton5ms.setColorNotPressed(colorNotPressed); - latencyCyton10ms.setColorNotPressed(isSelected_color); - latencyCyton20ms.setColorNotPressed(colorNotPressed); - hub.setLatency(LATENCY_10_MS); - wifiInternetProtocolCytonTCP.setColorNotPressed(colorNotPressed); - wifiInternetProtocolCytonUDP.setColorNotPressed(colorNotPressed); - wifiInternetProtocolCytonUDPBurst.setColorNotPressed(isSelected_color); - hub.setWifiInternetProtocol(UDP_BURST); - hub.setWiFiStyle(WIFI_DYNAMIC); + // WiFi autoconnect is used for "Dynamic IP" wifiIPAddressDynamic.setColorNotPressed(isSelected_color); wifiIPAddressStatic.setColorNotPressed(colorNotPressed); - } else if(newDataSource == DATASOURCE_GANGLION){ + } else if (eegDataSource == DATASOURCE_GANGLION) { updateToNChan(4); - latencyGanglion5ms.setColorNotPressed(colorNotPressed); - latencyGanglion10ms.setColorNotPressed(isSelected_color); - latencyGanglion20ms.setColorNotPressed(colorNotPressed); - hub.setLatency(LATENCY_10_MS); - wifiInternetProtocolGanglionTCP.setColorNotPressed(isSelected_color); - wifiInternetProtocolGanglionUDP.setColorNotPressed(colorNotPressed); - wifiInternetProtocolGanglionUDPBurst.setColorNotPressed(colorNotPressed); - hub.setWifiInternetProtocol(TCP); - hub.setWiFiStyle(WIFI_DYNAMIC); + // WiFi autoconnect is used for "Dynamic IP" wifiIPAddressDynamic.setColorNotPressed(isSelected_color); wifiIPAddressStatic.setColorNotPressed(colorNotPressed); - } else if(newDataSource == DATASOURCE_PLAYBACKFILE){ + } else if (eegDataSource == DATASOURCE_PLAYBACKFILE) { //GUI auto detects number of channels for playback when file is selected - } else if(newDataSource == DATASOURCE_SYNTHETIC){ - updateToNChan(8); + } else if (eegDataSource == DATASOURCE_SYNTHETIC) { synthChanButton4.setColorNotPressed(colorNotPressed); synthChanButton8.setColorNotPressed(isSelected_color); synthChanButton16.setColorNotPressed(colorNotPressed); + } else if (eegDataSource == DATASOURCE_NOVAXR) { + selectedSamplingRate = 250; //default sampling rate } - - //output("The new data source is " + str + " and NCHAN = [" + nchan + "]. "); //This text has been added to Init 5 checkpoint messages in first tab } if (theEvent.isFrom("serialList")) { Map bob = ((MenuList)theEvent.getController()).getItem(int(theEvent.getValue())); - openBCI_portName = (String)bob.get("headline"); + openBCI_portName = (String)bob.get("subline"); output("OpenBCI Port Name = " + openBCI_portName); } @@ -222,19 +175,21 @@ public void controlEvent(ControlEvent theEvent) { if (theEvent.isFrom("wifiList")) { Map bob = ((MenuList)theEvent.getController()).getItem(int(theEvent.getValue())); wifi_portName = (String)bob.get("headline"); - output("Wifi Device Name = " + wifi_portName); + wifi_ipAddress = (String)bob.get("subline"); + output("Selected WiFi Board: " + wifi_portName+ ", WiFi IP Address: " + wifi_ipAddress ); } - if (theEvent.isFrom("sdTimes")) { - Map bob = ((MenuList)theEvent.getController()).getItem(int(theEvent.getValue())); - sdSettingString = (String)bob.get("headline"); - sdSetting = int(theEvent.getValue()); - if (sdSetting != 0) { - output("OpenBCI microSD Setting = " + sdSettingString + " recording time"); - } else { - output("OpenBCI microSD Setting = " + sdSettingString); + // This dropdown menu sets Cyton maximum SD-Card file size (for users doing very long recordings) + if (theEvent.isFrom("sdCardTimes")) { + int val = (int)(theEvent.getController()).getValue(); + Map bob = ((ScrollableList)theEvent.getController()).getItem(val); + cyton_sdSetting = (CytonSDMode)bob.get("value"); + String outputString = "OpenBCI microSD Setting = " + cyton_sdSetting.getName(); + if (cyton_sdSetting != CytonSDMode.NO_WRITE) { + outputString += " recording time"; } - verbosePrint("SD setting = " + sdSetting); + output(outputString); + verbosePrint("SD Command = " + cyton_sdSetting.getCommand()); } if (theEvent.isFrom("channelListCP")) { @@ -243,10 +198,10 @@ public void controlEvent(ControlEvent theEvent) { cp5Popup.get(MenuList.class, "channelListCP").setVisible(false); channelPopup.setClicked(false); if (setChannel.wasPressed) { - set_channel(rcBox, setChannelInt); + rcBox.setChannel(setChannelInt); setChannel.wasPressed = false; } else if(ovrChannel.wasPressed) { - set_channel_over(rcBox, setChannelInt); + rcBox.setChannelOverride(setChannelInt); ovrChannel.wasPressed = false; } } @@ -257,12 +212,38 @@ public void controlEvent(ControlEvent theEvent) { //println("got a menu event from item " + s); String filePath = controlPanel.recentPlaybackBox.longFilePaths.get(s); if (new File(filePath).isFile()) { - playbackFileSelected(filePath, s); + playbackFileFromList(filePath, s); } else { + verbosePrint("Playback History: " + filePath); outputError("Playback History: Selected file does not exist. Try another file or clear settings to remove this entry."); } } + //Check for event in NovaXR Mode List in Control Panel + if (theEvent.isFrom("novaXR_SampleRates")) { + int val = (int)(theEvent.getController()).getValue(); + Map bob = ((ScrollableList)theEvent.getController()).getItem(val); + // this will retrieve the enum object stored in the dropdown! + novaXR_sampleRate = (NovaXRSR)bob.get("value"); + println("ControlPanel: User selected NovaXR Sample Rate: " + novaXR_sampleRate.getName()); + } + + //Check for event in NovaXR Mode List in Control Panel + if (theEvent.isFrom("novaXR_Modes")) { + int val = (int)(theEvent.getController()).getValue(); + Map bob = ((ScrollableList)theEvent.getController()).getItem(val); + // this will retrieve the enum object stored in the dropdown! + novaXR_boardSetting = (NovaXRMode)bob.get("value"); + println("ControlPanel: User selected NovaXR Mode: " + novaXR_boardSetting.getName()); + } + + //This dropdown is in the SessionData Box + if (theEvent.isFrom("maxFileDuration")) { + int n = (int)theEvent.getValue(); + settings.setLogFileDurationChoice(n); + println("ControlPanel: Chosen Recording Duration: " + n); + } + //Check control events from widgets if (systemMode >= SYSTEMMODE_POSTINIT) { //Check for event in PlaybackHistory Widget MenuList @@ -309,8 +290,7 @@ class ControlPanel { SyntheticChannelCountBox synthChannelCountBox; RecentPlaybackBox recentPlaybackBox; PlaybackFileBox playbackFileBox; - SDConverterBox sdConverterBox; - NoHubBox noHubBox; + NovaXRBox novaXRBox; BLEBox bleBox; SessionDataBox dataLogBoxGanglion; WifiBox wifiBox; @@ -318,12 +298,13 @@ class ControlPanel { InterfaceBoxGanglion interfaceBoxGanglion; SampleRateCytonBox sampleRateCytonBox; SampleRateGanglionBox sampleRateGanglionBox; - LatencyCytonBox latencyCytonBox; - LatencyGanglionBox latencyGanglionBox; - WifiTransferProtcolCytonBox wifiTransferProtcolCytonBox; - WifiTransferProtcolGanglionBox wifiTransferProtcolGanglionBox; SDBox sdBox; + //Track Dynamic and Static WiFi mode in Control Panel + final public String WIFI_DYNAMIC = "dynamic"; + final public String WIFI_STATIC = "static"; + private String wifiSearchStyle = WIFI_DYNAMIC; + boolean drawStopInstructions; int globalPadding; //design feature: passed through to all box classes as the global spacing .. in pixels .. for all elements/subelements boolean convertingSD = false; @@ -351,7 +332,6 @@ class ControlPanel { interfaceBoxCyton = new InterfaceBoxCyton(x + w, dataSourceBox.y, w, h, globalPadding); interfaceBoxGanglion = new InterfaceBoxGanglion(x + w, dataSourceBox.y, w, h, globalPadding); - noHubBox = new NoHubBox(x + w, dataSourceBox.y, w, h, globalPadding); serialBox = new SerialBox(x + w, interfaceBoxCyton.y + interfaceBoxCyton.h, w, h, globalPadding); wifiBox = new WifiBox(x + w, interfaceBoxCyton.y + interfaceBoxCyton.h, w, h, globalPadding); @@ -360,35 +340,29 @@ class ControlPanel { synthChannelCountBox = new SyntheticChannelCountBox(x + w, dataSourceBox.y, w, h, globalPadding); sdBox = new SDBox(x + w, (channelCountBox.y + channelCountBox.h), w, h, globalPadding); sampleRateCytonBox = new SampleRateCytonBox(x + w + x + w - 3, channelCountBox.y, w, h, globalPadding); - latencyCytonBox = new LatencyCytonBox(x + w + x + w - 3, (sampleRateCytonBox.y + sampleRateCytonBox.h), w, h, globalPadding); - wifiTransferProtcolCytonBox = new WifiTransferProtcolCytonBox(x + w + x + w - 3, (latencyCytonBox.y + latencyCytonBox.h), w, h, globalPadding); - + //boxes active when eegDataSource = Playback int playbackWidth = int(w * 1.35); playbackFileBox = new PlaybackFileBox(x + w, dataSourceBox.y, playbackWidth, h, globalPadding); - sdConverterBox = new SDConverterBox(x + w, (playbackFileBox.y + playbackFileBox.h), playbackWidth, h, globalPadding); - recentPlaybackBox = new RecentPlaybackBox(x + w, (sdConverterBox.y + sdConverterBox.h), playbackWidth, h, globalPadding); + recentPlaybackBox = new RecentPlaybackBox(x + w, (playbackFileBox.y + playbackFileBox.h), playbackWidth, h, globalPadding); + novaXRBox = new NovaXRBox(x + w, dataSourceBox.y, w, h, globalPadding); + comPortBox = new ComPortBox(x+w*2, y, w, h, globalPadding); rcBox = new RadioConfigBox(x+w, y + comPortBox.h, w, h, globalPadding); channelPopup = new ChannelPopup(x+w, y, w, h, globalPadding); pollPopup = new PollPopup(x+w,y,w,h,globalPadding); - wcBox = new WifiConfigBox(x+w, y, w, h, globalPadding); - initBox = new InitBox(x, (dataSourceBox.y + dataSourceBox.h), w, h, globalPadding); // Ganglion bleBox = new BLEBox(x + w, interfaceBoxGanglion.y + interfaceBoxGanglion.h, w, h, globalPadding); dataLogBoxGanglion = new SessionDataBox(x + w, (bleBox.y + bleBox.h), w, h, globalPadding, DATASOURCE_GANGLION); sampleRateGanglionBox = new SampleRateGanglionBox(x + w, (dataLogBoxGanglion.y + dataLogBoxGanglion.h), w, h, globalPadding); - latencyGanglionBox = new LatencyGanglionBox(x + w, (sampleRateGanglionBox.y + sampleRateGanglionBox.h), w, h, globalPadding); - wifiTransferProtcolGanglionBox = new WifiTransferProtcolGanglionBox(x + w, (latencyGanglionBox.y + latencyGanglionBox.h), w, h, globalPadding); - // bleHardwareBox = new BLEHardwareBox(x + w, (dataLogBoxGanglion.y + dataLogBoxGanglion.h), w, h, globalPadding); } public void resetListItems(){ - serialList.activeItem = -1; + comPortBox.serialList.activeItem = -1; bleList.activeItem = -1; wifiList.activeItem = -1; } @@ -403,6 +377,14 @@ class ControlPanel { topNav.controlPanelCollapser.setIsActive(false); } + public String getWifiSearchStyle() { + return wifiSearchStyle; + } + + private void setWiFiSearchStyle(String s) { + wifiSearchStyle = s; + } + public void update() { //toggle view of cp5 / serial list selection table if (isOpen) { // if control panel is open @@ -417,12 +399,6 @@ class ControlPanel { } } - //auto-update serial list - if(Serial.list().length != serialPorts.length && systemMode != SYSTEMMODE_POSTINIT){ - println("Refreshing port list..."); - refreshPortList(); - } - //update all boxes if they need to be dataSourceBox.update(); serialBox.update(); @@ -434,32 +410,22 @@ class ControlPanel { //update playback box sizes when dropdown is selected recentPlaybackBox.update(); playbackFileBox.update(); - sdConverterBox.update(); + + novaXRBox.update(); sdBox.update(); rcBox.update(); comPortBox.update(); - wcBox.update(); initBox.update(); channelPopup.update(); - serialList.updateMenu(); bleList.updateMenu(); wifiList.updateMenu(); dataLogBoxGanglion.update(); - latencyCytonBox.update(); - wifiTransferProtcolCytonBox.update(); wifiBox.update(); interfaceBoxCyton.update(); interfaceBoxGanglion.update(); - latencyGanglionBox.update(); - wifiTransferProtcolGanglionBox.update(); - - //SD File Conversion - while (convertingSD == true) { - convertSDFile(); - } } public void draw() { @@ -481,121 +447,91 @@ class ControlPanel { cp5Popup.setVisible(true); if (eegDataSource == DATASOURCE_CYTON) { //when data source is from OpenBCI - if(!hub.isHubRunning()) { - noHubBox.draw(); - } - else if (cyton.getInterface() == INTERFACE_NONE) { + if (selectedProtocol == BoardProtocol.NONE) { interfaceBoxCyton.draw(); } else { interfaceBoxCyton.draw(); - if (cyton.getInterface() == INTERFACE_SERIAL) { + if (selectedProtocol == BoardProtocol.SERIAL) { serialBox.y = interfaceBoxCyton.y + interfaceBoxCyton.h; serialBox.draw(); dataLogBoxCyton.y = serialBox.y + serialBox.h; if (rcBox.isShowing) { comPortBox.draw(); rcBox.draw(); - cp5.get(MenuList.class, "serialList").setVisible(true); + comPortBox.serialList.setVisible(true); if (channelPopup.wasClicked()) { channelPopup.draw(); cp5Popup.get(MenuList.class, "channelListCP").setVisible(true); cp5Popup.get(MenuList.class, "pollList").setVisible(false); - cp5.get(MenuList.class, "serialList").setVisible(true); //make sure the serialList menulist is visible - //cp5.get(MenuList.class, "sdTimes").setVisible(true); //make sure the SD time record options menulist is visible } else if (pollPopup.wasClicked()) { pollPopup.draw(); cp5Popup.get(MenuList.class, "pollList").setVisible(true); cp5Popup.get(MenuList.class, "channelListCP").setVisible(false); cp5.get(Textfield.class, "fileNameCyton").setVisible(true); //make sure the data file field is visible - // cp5.get(Textfield.class, "fileNameGanglion").setVisible(true); //make sure the data file field is visible - cp5.get(MenuList.class, "serialList").setVisible(true); //make sure the serialList menulist is visible - //cp5.get(MenuList.class, "sdTimes").setVisible(true); //make sure the SD time record options menulist is visible cp5.get(Textfield.class, "staticIPAddress").setVisible(false); } } - } else if (cyton.getInterface() == INTERFACE_HUB_WIFI) { + } else if (selectedProtocol == BoardProtocol.WIFI) { wifiBox.y = interfaceBoxCyton.y + interfaceBoxCyton.h; wifiBox.draw(); dataLogBoxCyton.y = wifiBox.y + wifiBox.h; - if (hub.getWiFiStyle() == WIFI_STATIC) { + if (getWifiSearchStyle() == WIFI_STATIC) { cp5.get(Textfield.class, "staticIPAddress").setVisible(true); cp5.get(MenuList.class, "wifiList").setVisible(false); } else { cp5.get(Textfield.class, "staticIPAddress").setVisible(false); cp5.get(MenuList.class, "wifiList").setVisible(true); } - if(wcBox.isShowing){ - wcBox.draw(); - } sampleRateCytonBox.draw(); - latencyCytonBox.draw(); - wifiTransferProtcolCytonBox.draw(); } channelCountBox.y = dataLogBoxCyton.y + dataLogBoxCyton.h; sdBox.y = channelCountBox.y + channelCountBox.h; sampleRateCytonBox.y = channelCountBox.y; - latencyCytonBox.y = sampleRateCytonBox.y + sampleRateCytonBox.h; - wifiTransferProtcolCytonBox.y = latencyCytonBox.y + latencyCytonBox.h; channelCountBox.draw(); sdBox.draw(); cp5.get(Textfield.class, "fileNameCyton").setVisible(true); //make sure the data file field is visible cp5.get(Textfield.class, "fileNameGanglion").setVisible(false); //make sure the data file field is not visible - //cp5.get(MenuList.class, "sdTimes").setVisible(true); //make sure the SD time record options menulist is visible dataLogBoxCyton.draw(); //Drawing here allows max file size dropdown to be drawn on top } } else if (eegDataSource == DATASOURCE_PLAYBACKFILE) { //when data source is from playback file recentPlaybackBox.draw(); playbackFileBox.draw(); - sdConverterBox.draw(); //set other CP5 controllers invisible - // cp5.get(Textfield.class, "fileNameCyton").setVisible(false); //make sure the data file field is visible - // cp5.get(Textfield.class, "fileNameGanglion").setVisible(false); //make sure the data file field is visible - cp5.get(MenuList.class, "serialList").setVisible(false); - //cp5.get(MenuList.class, "sdTimes").setVisible(false); + comPortBox.serialList.setVisible(false); cp5Popup.get(MenuList.class, "channelListCP").setVisible(false); cp5Popup.get(MenuList.class, "pollList").setVisible(false); + } else if (eegDataSource == DATASOURCE_NOVAXR) { + novaXRBox.draw(); } else if (eegDataSource == DATASOURCE_SYNTHETIC) { //synthetic - //set other CP5 controllers invisible - // hideAllBoxes(); synthChannelCountBox.draw(); } else if (eegDataSource == DATASOURCE_GANGLION) { - if(!hub.isHubRunning()) { - noHubBox.draw(); - } - else if (ganglion.getInterface() == INTERFACE_NONE) { + if (selectedProtocol == BoardProtocol.NONE) { interfaceBoxGanglion.draw(); } else { interfaceBoxGanglion.draw(); - if (ganglion.getInterface() == INTERFACE_HUB_BLE || ganglion.getInterface() == INTERFACE_HUB_BLED112) { + if (selectedProtocol == BoardProtocol.BLED112) { bleBox.y = interfaceBoxGanglion.y + interfaceBoxGanglion.h; dataLogBoxGanglion.y = bleBox.y + bleBox.h; bleBox.draw(); cp5.get(MenuList.class, "bleList").setVisible(true); cp5.get(Textfield.class, "staticIPAddress").setVisible(false); - } else if (ganglion.getInterface() == INTERFACE_HUB_WIFI) { + } else if (selectedProtocol == BoardProtocol.WIFI) { wifiBox.y = interfaceBoxGanglion.y + interfaceBoxGanglion.h; dataLogBoxGanglion.y = wifiBox.y + wifiBox.h; wifiBox.draw(); - if (hub.getWiFiStyle() == WIFI_STATIC) { + if (getWifiSearchStyle() == WIFI_STATIC) { cp5.get(Textfield.class, "staticIPAddress").setVisible(true); cp5.get(MenuList.class, "wifiList").setVisible(false); } else { cp5.get(Textfield.class, "staticIPAddress").setVisible(false); cp5.get(MenuList.class, "wifiList").setVisible(true); } - if(wcBox.isShowing){ - wcBox.draw(); - } - latencyGanglionBox.y = dataLogBoxGanglion.y + dataLogBoxGanglion.h; - sampleRateGanglionBox.y = latencyGanglionBox.y + latencyGanglionBox.h; - wifiTransferProtcolGanglionBox.y = wifiTransferProtcolGanglionBox.y + wifiTransferProtcolGanglionBox.h; - latencyGanglionBox.draw(); + sampleRateGanglionBox.y = dataLogBoxGanglion.y +dataLogBoxGanglion.h; sampleRateGanglionBox.draw(); - wifiTransferProtcolGanglionBox.draw(); } dataLogBoxGanglion.draw(); //Drawing here allows max file size dropdown to be drawn on top cp5.get(Textfield.class, "fileNameCyton").setVisible(false); //make sure the data file field is visible @@ -633,9 +569,9 @@ class ControlPanel { //Drawing here allows max file size dropdown to be drawn on top of all other cp5 elements if (systemMode != 10 && outputDataSource == OUTPUT_SOURCE_ODF) { - if (eegDataSource == DATASOURCE_CYTON && cyton.getInterface() != INTERFACE_NONE) { + if (eegDataSource == DATASOURCE_CYTON && selectedProtocol != BoardProtocol.NONE) { dataLogBoxCyton.cp5_dataLog_dropdown.draw(); - } else if (eegDataSource == DATASOURCE_GANGLION && ganglion.getInterface() != INTERFACE_NONE) { + } else if (eegDataSource == DATASOURCE_GANGLION && selectedProtocol != BoardProtocol.NONE) { dataLogBoxGanglion.cp5_dataLog_dropdown.draw(); } } @@ -649,43 +585,18 @@ class ControlPanel { cp5Popup.hide(); // make sure to hide the controlP5 object cp5Popup.get(MenuList.class, "channelListCP").setVisible(false); cp5Popup.get(MenuList.class, "pollList").setVisible(false); - cp5.get(MenuList.class, "serialList").setVisible(false); - // cp5Popup.hide(); // make sure to hide the controlP5 object + comPortBox.serialList.setVisible(false); popOutRadioConfigButton.setString("Manual >"); - rcBox.print_onscreen(""); - if (board != null) { - board.stop(); - } - board = null; - } - - public void hideWifiPopoutBox() { - wcBox.isShowing = false; - popOutWifiConfigButton.setString(">"); - wcBox.updateMessage(""); - if (hub.isPortOpen()) hub.closePort(); - } - - public void refreshPortList(){ - serialPorts = new String[Serial.list().length]; - serialPorts = Serial.list(); - serialList.items.clear(); - for (int i = 0; i < serialPorts.length; i++) { - String tempPort = serialPorts[(serialPorts.length-1) - i]; //list backwards... because usually our port is at the bottom - serialList.addItem(makeItem(tempPort)); - } - serialList.updateMenu(); + rcBox.closeSerialPort(); } public void hideAllBoxes() { //set other CP5 controllers invisible - // cp5.get(Textfield.class, "fileNameCyton").setVisible(false); cp5.get(Textfield.class, "staticIPAddress").setVisible(false); cp5.get(Textfield.class, "fileNameGanglion").setVisible(false); - cp5.get(MenuList.class, "serialList").setVisible(false); + comPortBox.serialList.setVisible(false); cp5.get(MenuList.class, "bleList").setVisible(false); - //cp5.get(MenuList.class, "sdTimes").setVisible(false); cp5.get(MenuList.class, "wifiList").setVisible(false); cp5Popup.get(MenuList.class, "channelListCP").setVisible(false); cp5Popup.get(MenuList.class, "pollList").setVisible(false); @@ -703,388 +614,157 @@ class ControlPanel { //mouse pressed in control panel public void CPmousePressed() { - // verbosePrint("CPmousePressed"); + verbosePrint("CPmousePressed"); if (initSystemButton.isMouseHere()) { initSystemButton.setIsActive(true); initSystemButton.wasPressed = true; } - + //only able to click buttons of control panel when system is not running - if (systemMode != 10) { + if (systemMode != SYSTEMMODE_POSTINIT) { - if ((eegDataSource == DATASOURCE_CYTON || eegDataSource == DATASOURCE_GANGLION) && (cyton.isWifi() || ganglion.isWifi())) { - if(getIpAddress.isMouseHere()) { - getIpAddress.setIsActive(true); - getIpAddress.wasPressed = true; + //active buttons during DATASOURCE_CYTON + if (eegDataSource == DATASOURCE_CYTON) { + + if (selectedProtocol == BoardProtocol.SERIAL) { + if (popOutRadioConfigButton.isMouseHere()){ + popOutRadioConfigButton.setIsActive(true); + popOutRadioConfigButton.wasPressed = true; + } + if (refreshPort.isMouseHere()) { + refreshPort.setIsActive(true); + refreshPort.wasPressed = true; + } + if (serialBox.autoConnect.isMouseHere()) { + serialBox.autoConnect.setIsActive(true); + serialBox.autoConnect.wasPressed = true; + } } - if(getFirmwareVersion.isMouseHere()) { - getFirmwareVersion.setIsActive(true); - getFirmwareVersion.wasPressed = true; + if (chanButton8.isMouseHere()) { + chanButton8.setIsActive(true); + chanButton8.wasPressed = true; + chanButton8.setColorNotPressed(isSelected_color); + chanButton16.setColorNotPressed(colorNotPressed); //default color of button } - if(getMacAddress.isMouseHere()) { - getMacAddress.setIsActive(true); - getMacAddress.wasPressed = true; + if (chanButton16.isMouseHere()) { + chanButton16.setIsActive(true); + chanButton16.wasPressed = true; + chanButton8.setColorNotPressed(colorNotPressed); //default color of button + chanButton16.setColorNotPressed(isSelected_color); } - if(eraseCredentials.isMouseHere()) { - eraseCredentials.setIsActive(true); - eraseCredentials.wasPressed = true; + if (getChannel.isMouseHere()){ + getChannel.setIsActive(true); + getChannel.wasPressed = true; } - if(getTypeOfAttachedBoard.isMouseHere()) { - getTypeOfAttachedBoard.setIsActive(true); - getTypeOfAttachedBoard.wasPressed = true; + if (setChannel.isMouseHere()){ + setChannel.setIsActive(true); + setChannel.wasPressed = true; + ovrChannel.wasPressed = false; } - if (popOutWifiConfigButton.isMouseHere()){ - popOutWifiConfigButton.setIsActive(true); - popOutWifiConfigButton.wasPressed = true; + if (ovrChannel.isMouseHere()){ + ovrChannel.setIsActive(true); + ovrChannel.wasPressed = true; + setChannel.wasPressed = false; } - if(wifiIPAddressDynamic.isMouseHere()) { - wifiIPAddressDynamic.setIsActive(true); - wifiIPAddressDynamic.wasPressed = true; - wifiIPAddressDynamic.setColorNotPressed(isSelected_color); - wifiIPAddressStatic.setColorNotPressed(colorNotPressed); + if (protocolWifiCyton.isMouseHere()) { + protocolWifiCyton.setIsActive(true); + protocolWifiCyton.wasPressed = true; + protocolWifiCyton.setColorNotPressed(isSelected_color); + protocolSerialCyton.setColorNotPressed(colorNotPressed); } - if(wifiIPAddressStatic.isMouseHere()) { - wifiIPAddressStatic.setIsActive(true); - wifiIPAddressStatic.wasPressed = true; - wifiIPAddressStatic.setColorNotPressed(isSelected_color); - wifiIPAddressDynamic.setColorNotPressed(colorNotPressed); + if (protocolSerialCyton.isMouseHere()) { + protocolSerialCyton.setIsActive(true); + protocolSerialCyton.wasPressed = true; + protocolWifiCyton.setColorNotPressed(colorNotPressed); + protocolSerialCyton.setColorNotPressed(isSelected_color); } - } - // active button when the hub is not running - if (!hub.isHubRunning()) { - if (noHubShowDoc.isMouseHere()) { - noHubShowDoc.setIsActive(true); - noHubShowDoc.wasPressed = true; + if (autoscan.isMouseHere()){ + autoscan.setIsActive(true); + autoscan.wasPressed = true; } - } - //active buttons during DATASOURCE_CYTON - else if (eegDataSource == DATASOURCE_CYTON) { - - // active button when the hub is not running - if (!hub.isHubRunning()) { - if (noHubShowDoc.isMouseHere()) { - noHubShowDoc.setIsActive(true); - noHubShowDoc.wasPressed = true; - } - } else { - if (cyton.isSerial()) { - if (popOutRadioConfigButton.isMouseHere()){ - popOutRadioConfigButton.setIsActive(true); - popOutRadioConfigButton.wasPressed = true; - } - if (refreshPort.isMouseHere()) { - refreshPort.setIsActive(true); - refreshPort.wasPressed = true; - } - if (serialBox.autoConnect.isMouseHere()) { - serialBox.autoConnect.setIsActive(true); - serialBox.autoConnect.wasPressed = true; - } - } - - if (cyton.isWifi()) { - if (refreshWifi.isMouseHere()) { - refreshWifi.setIsActive(true); - refreshWifi.wasPressed = true; - } - } - - - if (autoSessionName.isMouseHere()) { - autoSessionName.setIsActive(true); - autoSessionName.wasPressed = true; - } - - if (outputODF.isMouseHere()) { - outputODF.setIsActive(true); - outputODF.wasPressed = true; - } - - if (outputBDF.isMouseHere()) { - outputBDF.setIsActive(true); - outputBDF.wasPressed = true; - } - - if (chanButton8.isMouseHere()) { - chanButton8.setIsActive(true); - chanButton8.wasPressed = true; - chanButton8.setColorNotPressed(isSelected_color); - chanButton16.setColorNotPressed(colorNotPressed); //default color of button - } - - if (chanButton16.isMouseHere()) { - chanButton16.setIsActive(true); - chanButton16.wasPressed = true; - chanButton8.setColorNotPressed(colorNotPressed); //default color of button - chanButton16.setColorNotPressed(isSelected_color); - } - - if (getChannel.isMouseHere()){ - getChannel.setIsActive(true); - getChannel.wasPressed = true; - } - - if (setChannel.isMouseHere()){ - setChannel.setIsActive(true); - setChannel.wasPressed = true; - ovrChannel.wasPressed = false; - } - - if (ovrChannel.isMouseHere()){ - ovrChannel.setIsActive(true); - ovrChannel.wasPressed = true; - setChannel.wasPressed = false; - } - - - - if (protocolWifiCyton.isMouseHere()) { - protocolWifiCyton.setIsActive(true); - protocolWifiCyton.wasPressed = true; - protocolWifiCyton.setColorNotPressed(isSelected_color); - protocolSerialCyton.setColorNotPressed(colorNotPressed); - } - - if (protocolSerialCyton.isMouseHere()) { - protocolSerialCyton.setIsActive(true); - protocolSerialCyton.wasPressed = true; - protocolWifiCyton.setColorNotPressed(colorNotPressed); - protocolSerialCyton.setColorNotPressed(isSelected_color); - } - - if (autoscan.isMouseHere()){ - autoscan.setIsActive(true); - autoscan.wasPressed = true; - } - if (systemStatus.isMouseHere()){ - systemStatus.setIsActive(true); - systemStatus.wasPressed = true; - } - - if (sampleRate250.isMouseHere()) { - sampleRate250.setIsActive(true); - sampleRate250.wasPressed = true; - sampleRate250.setColorNotPressed(isSelected_color); - sampleRate500.setColorNotPressed(colorNotPressed); - sampleRate1000.setColorNotPressed(colorNotPressed); //default color of button - } - - if (sampleRate500.isMouseHere()) { - sampleRate500.setIsActive(true); - sampleRate500.wasPressed = true; - sampleRate500.setColorNotPressed(isSelected_color); - sampleRate250.setColorNotPressed(colorNotPressed); - sampleRate1000.setColorNotPressed(colorNotPressed); //default color of button - } - - if (sampleRate1000.isMouseHere()) { - sampleRate1000.setIsActive(true); - sampleRate1000.wasPressed = true; - sampleRate1000.setColorNotPressed(isSelected_color); - sampleRate250.setColorNotPressed(colorNotPressed); //default color of button - sampleRate500.setColorNotPressed(colorNotPressed); - } - - if (latencyCyton5ms.isMouseHere()) { - latencyCyton5ms.setIsActive(true); - latencyCyton5ms.wasPressed = true; - latencyCyton5ms.setColorNotPressed(isSelected_color); - latencyCyton10ms.setColorNotPressed(colorNotPressed); //default color of button - latencyCyton20ms.setColorNotPressed(colorNotPressed); //default color of button - } - - if (latencyCyton10ms.isMouseHere()) { - latencyCyton10ms.setIsActive(true); - latencyCyton10ms.wasPressed = true; - latencyCyton10ms.setColorNotPressed(isSelected_color); - latencyCyton5ms.setColorNotPressed(colorNotPressed); //default color of button - latencyCyton20ms.setColorNotPressed(colorNotPressed); //default color of button - } - - if (latencyCyton20ms.isMouseHere()) { - latencyCyton20ms.setIsActive(true); - latencyCyton20ms.wasPressed = true; - latencyCyton20ms.setColorNotPressed(isSelected_color); - latencyCyton5ms.setColorNotPressed(colorNotPressed); //default color of button - latencyCyton10ms.setColorNotPressed(colorNotPressed); //default color of button - } + if (systemStatus.isMouseHere()){ + systemStatus.setIsActive(true); + systemStatus.wasPressed = true; + } - if (wifiInternetProtocolCytonTCP.isMouseHere()) { - wifiInternetProtocolCytonTCP.setIsActive(true); - wifiInternetProtocolCytonTCP.wasPressed = true; - wifiInternetProtocolCytonTCP.setColorNotPressed(isSelected_color); - wifiInternetProtocolCytonUDP.setColorNotPressed(colorNotPressed); //default color of button - wifiInternetProtocolCytonUDPBurst.setColorNotPressed(colorNotPressed); //default color of button - } + if (sampleRate250.isMouseHere()) { + sampleRate250.setIsActive(true); + sampleRate250.wasPressed = true; + sampleRate250.setColorNotPressed(isSelected_color); + sampleRate500.setColorNotPressed(colorNotPressed); + sampleRate1000.setColorNotPressed(colorNotPressed); //default color of button + } - if (wifiInternetProtocolCytonUDP.isMouseHere()) { - wifiInternetProtocolCytonUDP.setIsActive(true); - wifiInternetProtocolCytonUDP.wasPressed = true; - wifiInternetProtocolCytonUDP.setColorNotPressed(isSelected_color); - wifiInternetProtocolCytonTCP.setColorNotPressed(colorNotPressed); //default color of button - wifiInternetProtocolCytonUDPBurst.setColorNotPressed(colorNotPressed); //default color of button - } + if (sampleRate500.isMouseHere()) { + sampleRate500.setIsActive(true); + sampleRate500.wasPressed = true; + sampleRate500.setColorNotPressed(isSelected_color); + sampleRate250.setColorNotPressed(colorNotPressed); + sampleRate1000.setColorNotPressed(colorNotPressed); //default color of button + } - if (wifiInternetProtocolCytonUDPBurst.isMouseHere()) { - wifiInternetProtocolCytonUDPBurst.setIsActive(true); - wifiInternetProtocolCytonUDPBurst.wasPressed = true; - wifiInternetProtocolCytonUDPBurst.setColorNotPressed(isSelected_color); - wifiInternetProtocolCytonTCP.setColorNotPressed(colorNotPressed); //default color of button - wifiInternetProtocolCytonUDP.setColorNotPressed(colorNotPressed); //default color of button - } + if (sampleRate1000.isMouseHere()) { + sampleRate1000.setIsActive(true); + sampleRate1000.wasPressed = true; + sampleRate1000.setColorNotPressed(isSelected_color); + sampleRate250.setColorNotPressed(colorNotPressed); //default color of button + sampleRate500.setColorNotPressed(colorNotPressed); } } else if (eegDataSource == DATASOURCE_GANGLION) { + // This is where we check for button presses if we are searching for BLE devices + + if (refreshBLE.isMouseHere()) { + refreshBLE.setIsActive(true); + refreshBLE.wasPressed = true; + } - // active button when the hub is not running - if (!hub.isHubRunning()) { - if (noHubShowDoc.isMouseHere()) { - noHubShowDoc.setIsActive(true); - noHubShowDoc.wasPressed = true; - } - } else { - // This is where we check for button presses if we are searching for BLE devices - if (autoSessionName.isMouseHere()) { - autoSessionName.setIsActive(true); - autoSessionName.wasPressed = true; - } - - if (outputODF.isMouseHere()) { - outputODF.setIsActive(true); - outputODF.wasPressed = true; - } - - if (outputBDF.isMouseHere()) { - outputBDF.setIsActive(true); - outputBDF.wasPressed = true; - } - - if (ganglion.isWifi()) { - if (refreshWifi.isMouseHere()) { - refreshWifi.setIsActive(true); - refreshWifi.wasPressed = true; - } - } else { - if (refreshBLE.isMouseHere()) { - refreshBLE.setIsActive(true); - refreshBLE.wasPressed = true; - } - } - - // this button only used on mac - if (isMac() && protocolBLEGanglion.isMouseHere()) { - protocolBLEGanglion.setIsActive(true); - protocolBLEGanglion.wasPressed = true; - protocolBLED112Ganglion.setColorNotPressed(colorNotPressed); - protocolBLEGanglion.setColorNotPressed(isSelected_color); - protocolWifiGanglion.setColorNotPressed(colorNotPressed); - } - - if (protocolWifiGanglion.isMouseHere()) { - protocolWifiGanglion.setIsActive(true); - protocolWifiGanglion.wasPressed = true; - protocolBLED112Ganglion.setColorNotPressed(colorNotPressed); - protocolWifiGanglion.setColorNotPressed(isSelected_color); - if(isMac()) { - protocolBLEGanglion.setColorNotPressed(colorNotPressed); - } - } - - if (protocolBLED112Ganglion.isMouseHere()) { - protocolBLED112Ganglion.setIsActive(true); - protocolBLED112Ganglion.wasPressed = true; - if(isMac()) { - protocolBLEGanglion.setColorNotPressed(colorNotPressed); - } - protocolBLED112Ganglion.setColorNotPressed(isSelected_color); - protocolWifiGanglion.setColorNotPressed(colorNotPressed); - } - - if (sampleRate200.isMouseHere()) { - sampleRate200.setIsActive(true); - sampleRate200.wasPressed = true; - sampleRate200.setColorNotPressed(isSelected_color); - sampleRate1600.setColorNotPressed(colorNotPressed); //default color of button - } - - if (sampleRate1600.isMouseHere()) { - sampleRate1600.setIsActive(true); - sampleRate1600.wasPressed = true; - sampleRate1600.setColorNotPressed(isSelected_color); - sampleRate200.setColorNotPressed(colorNotPressed); //default color of button - } - - if (latencyGanglion5ms.isMouseHere()) { - latencyGanglion5ms.setIsActive(true); - latencyGanglion5ms.wasPressed = true; - latencyGanglion5ms.setColorNotPressed(isSelected_color); - latencyGanglion10ms.setColorNotPressed(colorNotPressed); //default color of button - latencyGanglion20ms.setColorNotPressed(colorNotPressed); //default color of button - } - - if (latencyGanglion10ms.isMouseHere()) { - latencyGanglion10ms.setIsActive(true); - latencyGanglion10ms.wasPressed = true; - latencyGanglion10ms.setColorNotPressed(isSelected_color); - latencyGanglion5ms.setColorNotPressed(colorNotPressed); //default color of button - latencyGanglion20ms.setColorNotPressed(colorNotPressed); //default color of button - } - - if (latencyGanglion20ms.isMouseHere()) { - latencyGanglion20ms.setIsActive(true); - latencyGanglion20ms.wasPressed = true; - latencyGanglion20ms.setColorNotPressed(isSelected_color); - latencyGanglion5ms.setColorNotPressed(colorNotPressed); //default color of button - latencyGanglion10ms.setColorNotPressed(colorNotPressed); //default color of button - } + if (protocolWifiGanglion.isMouseHere()) { + protocolWifiGanglion.setIsActive(true); + protocolWifiGanglion.wasPressed = true; + protocolBLED112Ganglion.setColorNotPressed(colorNotPressed); + protocolWifiGanglion.setColorNotPressed(isSelected_color); + } - if (wifiInternetProtocolGanglionTCP.isMouseHere()) { - wifiInternetProtocolGanglionTCP.setIsActive(true); - wifiInternetProtocolGanglionTCP.wasPressed = true; - wifiInternetProtocolGanglionTCP.setColorNotPressed(isSelected_color); - wifiInternetProtocolGanglionUDP.setColorNotPressed(colorNotPressed); //default color of button - wifiInternetProtocolGanglionUDPBurst.setColorNotPressed(colorNotPressed); //default color of button - } + if (protocolBLED112Ganglion.isMouseHere()) { + protocolBLED112Ganglion.setIsActive(true); + protocolBLED112Ganglion.wasPressed = true; + protocolBLED112Ganglion.setColorNotPressed(isSelected_color); + protocolWifiGanglion.setColorNotPressed(colorNotPressed); + } - if (wifiInternetProtocolGanglionUDP.isMouseHere()) { - wifiInternetProtocolGanglionUDP.setIsActive(true); - wifiInternetProtocolGanglionUDP.wasPressed = true; - wifiInternetProtocolGanglionUDP.setColorNotPressed(isSelected_color); - wifiInternetProtocolGanglionTCP.setColorNotPressed(colorNotPressed); //default color of button - wifiInternetProtocolGanglionUDPBurst.setColorNotPressed(colorNotPressed); //default color of button - } + if (sampleRate200.isMouseHere()) { + sampleRate200.setIsActive(true); + sampleRate200.wasPressed = true; + sampleRate200.setColorNotPressed(isSelected_color); + sampleRate1600.setColorNotPressed(colorNotPressed); //default color of button + } - if (wifiInternetProtocolGanglionUDPBurst.isMouseHere()) { - wifiInternetProtocolGanglionUDPBurst.setIsActive(true); - wifiInternetProtocolGanglionUDPBurst.wasPressed = true; - wifiInternetProtocolGanglionUDPBurst.setColorNotPressed(isSelected_color); - wifiInternetProtocolGanglionTCP.setColorNotPressed(colorNotPressed); //default color of button - wifiInternetProtocolGanglionUDP.setColorNotPressed(colorNotPressed); //default color of button - } + if (sampleRate1600.isMouseHere()) { + sampleRate1600.setIsActive(true); + sampleRate1600.wasPressed = true; + sampleRate1600.setColorNotPressed(isSelected_color); + sampleRate200.setColorNotPressed(colorNotPressed); //default color of button } } //active buttons during DATASOURCE_PLAYBACKFILE - if (eegDataSource == DATASOURCE_PLAYBACKFILE) { + else if (eegDataSource == DATASOURCE_PLAYBACKFILE) { if (selectPlaybackFile.isMouseHere()) { selectPlaybackFile.setIsActive(true); selectPlaybackFile.wasPressed = true; } - if (selectSDFile.isMouseHere()) { - selectSDFile.setIsActive(true); - selectSDFile.wasPressed = true; - } if (sampleDataButton.isMouseHere()) { sampleDataButton.setIsActive(true); sampleDataButton.wasPressed = true; @@ -1092,7 +772,7 @@ class ControlPanel { } //active buttons during DATASOURCE_SYNTHETIC - if (eegDataSource == DATASOURCE_SYNTHETIC) { + else if (eegDataSource == DATASOURCE_SYNTHETIC) { if (synthChanButton4.isMouseHere()) { synthChanButton4.setIsActive(true); synthChanButton4.wasPressed = true; @@ -1118,6 +798,47 @@ class ControlPanel { } } + else if (eegDataSource == DATASOURCE_NOVAXR) { + novaXRBox.mousePressed(); + } + + + //The following buttons apply only to Cyton and Ganglion Modes for now + if (autoSessionName.isMouseHere()) { + autoSessionName.setIsActive(true); + autoSessionName.wasPressed = true; + } + + if (outputODF.isMouseHere()) { + outputODF.setIsActive(true); + outputODF.wasPressed = true; + } + + if (outputBDF.isMouseHere()) { + outputBDF.setIsActive(true); + outputBDF.wasPressed = true; + } + + if (selectedProtocol == BoardProtocol.WIFI) { + if (refreshWifi.isMouseHere()) { + refreshWifi.setIsActive(true); + refreshWifi.wasPressed = true; + } + if (wifiIPAddressDynamic.isMouseHere()) { + wifiIPAddressDynamic.setIsActive(true); + wifiIPAddressDynamic.wasPressed = true; + wifiIPAddressDynamic.setColorNotPressed(isSelected_color); + wifiIPAddressStatic.setColorNotPressed(colorNotPressed); + } + + if (wifiIPAddressStatic.isMouseHere()) { + wifiIPAddressStatic.setIsActive(true); + wifiIPAddressStatic.wasPressed = true; + wifiIPAddressStatic.setColorNotPressed(isSelected_color); + wifiIPAddressDynamic.setColorNotPressed(colorNotPressed); + } + } + } // output("Text File Name: " + cp5.get(Textfield.class,"fileNameCyton").getText()); } @@ -1128,7 +849,7 @@ class ControlPanel { if (popOutRadioConfigButton.isMouseHere() && popOutRadioConfigButton.wasPressed) { popOutRadioConfigButton.wasPressed = false; popOutRadioConfigButton.setIsActive(false); - if (cyton.isSerial()) { + if (selectedProtocol == BoardProtocol.SERIAL) { if (rcBox.isShowing) { hideRadioPopoutBox(); serialBox.autoConnect.setIgnoreHover(false); @@ -1146,13 +867,12 @@ class ControlPanel { if (serialBox.autoConnect.isMouseHere() && serialBox.autoConnect.wasPressed) { serialBox.autoConnect.wasPressed = false; serialBox.autoConnect.setIsActive(false); - serialBox.attemptAutoConnectCyton(); + comPortBox.attemptAutoConnectCyton(); } if (rcBox.isShowing) { if(getChannel.isMouseHere() && getChannel.wasPressed){ - // if(board != null) // Radios_Config will handle creating the serial port JAM 1/2017 - get_channel(rcBox); + rcBox.getChannel(); getChannel.wasPressed = false; getChannel.setIsActive(false); hideChannelListCP(); @@ -1173,225 +893,93 @@ class ControlPanel { } if(autoscan.isMouseHere() && autoscan.wasPressed){ + rcBox.scanChannels(); autoscan.wasPressed = false; autoscan.setIsActive(false); - scan_channels(rcBox); hideChannelListCP(); } if(systemStatus.isMouseHere() && systemStatus.wasPressed){ - system_status(rcBox); + rcBox.getSystemStatus(); systemStatus.setIsActive(false); systemStatus.wasPressed = false; hideChannelListCP(); } } - if(popOutWifiConfigButton.isMouseHere() && popOutWifiConfigButton.wasPressed){ - popOutWifiConfigButton.wasPressed = false; - popOutWifiConfigButton.setIsActive(false); - if (cyton.isWifi() || ganglion.isWifi()) { - if(wcBox.isShowing){ - hideWifiPopoutBox(); - } else { - if (hub.getWiFiStyle() == WIFI_STATIC) { - wifi_ipAddress = cp5.get(Textfield.class, "staticIPAddress").getText(); - println("Static IP address of " + wifi_ipAddress); - output("Static IP address of " + wifi_ipAddress); - hub.examineWifi(wifi_ipAddress); - wcBox.isShowing = true; - popOutWifiConfigButton.setString("<"); - } else { - if (wifi_portName == "N/A") { - output("Please select a WiFi Shield first. Can't see your WiFi Shield? Learn how at openbci.github.io/Documentation/"); - } else { - output("Attempting to connect to WiFi Shield named " + wifi_portName); - hub.examineWifi(wifi_portName); - wcBox.isShowing = true; - popOutWifiConfigButton.setString("<"); - } - } - } - } - } - - if (wcBox.isShowing) { - if(getIpAddress.isMouseHere() && getIpAddress.wasPressed){ - hub.getWifiInfo(TCP_WIFI_GET_IP_ADDRESS); - getIpAddress.wasPressed = false; - getIpAddress.setIsActive(false); - } - - if(getFirmwareVersion.isMouseHere() && getFirmwareVersion.wasPressed){ - hub.getWifiInfo(TCP_WIFI_GET_FIRMWARE_VERSION); - getFirmwareVersion.wasPressed = false; - getFirmwareVersion.setIsActive(false); - } - - if(getMacAddress.isMouseHere() && getMacAddress.wasPressed){ - hub.getWifiInfo(TCP_WIFI_GET_MAC_ADDRESS); - getMacAddress.wasPressed = false; - getMacAddress.setIsActive(false); - } - - if(eraseCredentials.isMouseHere() && eraseCredentials.wasPressed){ - hub.getWifiInfo(TCP_WIFI_ERASE_CREDENTIALS); - eraseCredentials.wasPressed=false; - eraseCredentials.setIsActive(false); - } - - if(getTypeOfAttachedBoard.isMouseHere() && getTypeOfAttachedBoard.wasPressed){ - // Wifi_Config will handle creating the connection - hub.getWifiInfo(TCP_WIFI_GET_TYPE_OF_ATTACHED_BOARD); - getTypeOfAttachedBoard.wasPressed=false; - getTypeOfAttachedBoard.setIsActive(false); - } - } - if (initSystemButton.isMouseHere() && initSystemButton.wasPressed) { if (rcBox.isShowing) { hideRadioPopoutBox(); } - if (wcBox.isShowing) { - hideWifiPopoutBox(); - } //if system is not active ... initate system and flip button state initButtonPressed(); //cursor(ARROW); //this this back to ARROW } - if ((eegDataSource == DATASOURCE_CYTON || eegDataSource == DATASOURCE_GANGLION)) { - if (noHubShowDoc.isMouseHere() && noHubShowDoc.wasPressed) { - noHubShowDoc.wasPressed=false; - noHubShowDoc.setIsActive(false); - noHubShowDoc.goToURL(); - } - } - //open or close serial port if serial port button is pressed (left button in serial widget) if (refreshPort.isMouseHere() && refreshPort.wasPressed) { - output("Serial/COM List Refreshed"); - refreshPortList(); + comPortBox.refreshPortListCyton(); } if (refreshBLE.isMouseHere() && refreshBLE.wasPressed) { - if (isHubObjectInitialized) { - output("BLE Devices Refreshing"); - bleList.items.clear(); - hub.searchDeviceStart(); - } else { - output("Please wait till BLE is fully initalized"); - } + bleBox.refreshGanglionBLEList(); } if (refreshWifi.isMouseHere() && refreshWifi.wasPressed) { - if (isHubObjectInitialized) { - output("Wifi Devices Refreshing"); - wifiList.items.clear(); - hub.searchDeviceStart(); - } else { - output("Please wait till hub is fully initalized"); - } + wifiBox.refreshWifiList(); } + // Dynamic = Autoconnect, Static = Manually type IP address if(wifiIPAddressDynamic.isMouseHere() && wifiIPAddressDynamic.wasPressed) { - hub.setWiFiStyle(WIFI_DYNAMIC); - wifiBox.h = 200; - String output = "Using " + (hub.getWiFiStyle() == WIFI_STATIC ? "Static" : "Dynamic") + " IP address of the WiFi Shield!"; - outputInfo(output); + wifiBox.h = 208; + setWiFiSearchStyle(WIFI_DYNAMIC); + String output = "Using Dynamic IP address of the WiFi Shield!"; println("CP: WiFi IP: " + output); } if(wifiIPAddressStatic.isMouseHere() && wifiIPAddressStatic.wasPressed) { - hub.setWiFiStyle(WIFI_STATIC); wifiBox.h = 120; - String output = "Using " + (hub.getWiFiStyle() == WIFI_STATIC ? "Static" : "Dynamic") + " IP address of the WiFi Shield!"; + setWiFiSearchStyle(WIFI_STATIC); + String output = "Using Static IP address of the WiFi Shield!"; outputInfo(output); println("CP: WiFi IP: " + output); } - // this button only used on mac - if (isMac() && protocolBLEGanglion.isMouseHere() && protocolBLEGanglion.wasPressed) { - println("protocolBLEGanglion"); - - wifiList.items.clear(); - bleList.items.clear(); - controlPanel.hideAllBoxes(); - if (isHubObjectInitialized) { - outputSuccess("Using built in BLE for Ganglion"); - if (hub.isPortOpen()) hub.closePort(); - ganglion.setInterface(INTERFACE_HUB_BLE); - // hub.searchDeviceStart(); - } else { - outputWarn("Please wait till hub is fully initalized"); - } - } - if (protocolBLED112Ganglion.isMouseHere() && protocolBLED112Ganglion.wasPressed) { - wifiList.items.clear(); bleList.items.clear(); controlPanel.hideAllBoxes(); - if (isHubObjectInitialized) { - output("Protocol BLED112 Selected for Ganglion"); - if (hub.isPortOpen()) hub.closePort(); - ganglion.setInterface(INTERFACE_HUB_BLED112); - // hub.searchDeviceStart(); - } else { - outputWarn("Please wait till hub is fully initalized"); - } + selectedProtocol = BoardProtocol.BLED112; + bleBox.refreshGanglionBLEList(); } if (protocolWifiGanglion.isMouseHere() && protocolWifiGanglion.wasPressed) { - println("protocolWifiGanglion"); wifiList.items.clear(); bleList.items.clear(); controlPanel.hideAllBoxes(); - println("isHubObjectInitialized: " + (isHubObjectInitialized ? "true" : "else")); - if (isHubObjectInitialized) { - output("Protocol Wifi Selected for Ganglion"); - if (hub.isPortOpen()) hub.closePort(); - ganglion.setInterface(INTERFACE_HUB_WIFI); - hub.searchDeviceStart(); - } else { - output("Please wait till hub is fully initalized"); - } + selectedProtocol = BoardProtocol.WIFI; } if (protocolSerialCyton.isMouseHere() && protocolSerialCyton.wasPressed) { wifiList.items.clear(); bleList.items.clear(); controlPanel.hideAllBoxes(); - if (isHubObjectInitialized) { - output("Protocol Serial Selected for Cyton"); - if (hub.isPortOpen()) hub.closePort(); - cyton.setInterface(INTERFACE_SERIAL); - } else { - output("Please wait till hub is fully initalized"); - } + selectedProtocol = BoardProtocol.SERIAL; + comPortBox.refreshPortListCyton(); } if (protocolWifiCyton.isMouseHere() && protocolWifiCyton.wasPressed) { wifiList.items.clear(); bleList.items.clear(); controlPanel.hideAllBoxes(); - if (isHubObjectInitialized) { - output("Protocol Wifi Selected for Cyton"); - if (hub.isPortOpen()) hub.closePort(); - cyton.setInterface(INTERFACE_HUB_WIFI); - hub.searchDeviceStart(); - } else { - output("Please wait till hub is fully initalized"); - } + selectedProtocol = BoardProtocol.WIFI; } - - if (autoSessionName.isMouseHere() && autoSessionName.wasPressed) { String _board = (eegDataSource == DATASOURCE_CYTON) ? "Cyton" : "Ganglion"; String _textField = (eegDataSource == DATASOURCE_CYTON) ? "fileNameCyton" : "fileNameGanglion"; output("Autogenerated " + _board + " Session Name based on current date & time."); - cp5.get(Textfield.class, _textField).setText(getDateString()); + cp5.get(Textfield.class, _textField).setText(directoryManager.getFileNameDateTime()); } if (outputODF.isMouseHere() && outputODF.wasPressed) { @@ -1427,23 +1015,23 @@ class ControlPanel { } if (sampleRate200.isMouseHere() && sampleRate200.wasPressed) { - ganglion.setSampleRate(200); + selectedSamplingRate = 200; } if (sampleRate1600.isMouseHere() && sampleRate1600.wasPressed) { - ganglion.setSampleRate(1600); + selectedSamplingRate = 1600; } if (sampleRate250.isMouseHere() && sampleRate250.wasPressed) { - cyton.setSampleRate(250); + selectedSamplingRate = 250; } if (sampleRate500.isMouseHere() && sampleRate500.wasPressed) { - cyton.setSampleRate(500); + selectedSamplingRate = 500; } if (sampleRate1000.isMouseHere() && sampleRate1000.wasPressed) { - cyton.setSampleRate(1000); + selectedSamplingRate = 1000; } if (synthChanButton4.isMouseHere() && synthChanButton4.wasPressed) { @@ -1458,92 +1046,32 @@ class ControlPanel { updateToNChan(16); } - if (latencyCyton5ms.isMouseHere() && latencyCyton5ms.wasPressed) { - hub.setLatency(LATENCY_5_MS); - } - - if (latencyCyton10ms.isMouseHere() && latencyCyton10ms.wasPressed) { - hub.setLatency(LATENCY_10_MS); - } - - if (latencyCyton20ms.isMouseHere() && latencyCyton20ms.wasPressed) { - hub.setLatency(LATENCY_20_MS); - } - - if (latencyGanglion5ms.isMouseHere() && latencyGanglion5ms.wasPressed) { - hub.setLatency(LATENCY_5_MS); - } - - if (latencyGanglion10ms.isMouseHere() && latencyGanglion10ms.wasPressed) { - hub.setLatency(LATENCY_10_MS); - } - - if (latencyGanglion20ms.isMouseHere() && latencyGanglion20ms.wasPressed) { - hub.setLatency(LATENCY_20_MS); - } - - if (wifiInternetProtocolCytonTCP.isMouseHere() && wifiInternetProtocolCytonTCP.wasPressed) { - hub.setWifiInternetProtocol(TCP); - } - - if (wifiInternetProtocolCytonUDP.isMouseHere() && wifiInternetProtocolCytonUDP.wasPressed) { - hub.setWifiInternetProtocol(UDP); - } - - if (wifiInternetProtocolCytonUDPBurst.isMouseHere() && wifiInternetProtocolCytonUDPBurst.wasPressed) { - hub.setWifiInternetProtocol(UDP_BURST); - } - - if (wifiInternetProtocolGanglionTCP.isMouseHere() && wifiInternetProtocolGanglionTCP.wasPressed) { - hub.setWifiInternetProtocol(TCP); - } - - if (wifiInternetProtocolGanglionUDP.isMouseHere() && wifiInternetProtocolGanglionUDP.wasPressed) { - hub.setWifiInternetProtocol(UDP); - } - - if (wifiInternetProtocolGanglionUDPBurst.isMouseHere() && wifiInternetProtocolGanglionUDPBurst.wasPressed) { - hub.setWifiInternetProtocol(UDP_BURST); - } - if (selectPlaybackFile.isMouseHere() && selectPlaybackFile.wasPressed) { output("Select a file for playback"); selectInput("Select a pre-recorded file for playback:", "playbackFileSelected", - new File(settings.guiDataPath + "Recordings")); + new File(directoryManager.getGuiDataPath() + "Recordings")); } - if (selectSDFile.isMouseHere() && selectSDFile.wasPressed) { - output("Select an SD file to convert to a playback file"); - createPlaybackFileFromSD(); - selectInput("Select an SD file to convert for playback:", "sdFileSelected"); - } if (sampleDataButton.isMouseHere() && sampleDataButton.wasPressed) { output("Select a file for playback"); selectInput("Select a pre-recorded file for playback:", "playbackFileSelected", - new File(settings.guiDataPath + + new File(directoryManager.getGuiDataPath() + "Sample_Data" + System.getProperty("file.separator") + "OpenBCI-sampleData-2-meditation.txt")); } + novaXRBox.mouseReleased(); + //reset all buttons to false - noHubShowDoc.setIsActive(false); - noHubShowDoc.wasPressed = false; refreshPort.setIsActive(false); refreshPort.wasPressed = false; refreshBLE.setIsActive(false); refreshBLE.wasPressed = false; refreshWifi.setIsActive(false); refreshWifi.wasPressed = false; - - // this button used on mac only - if (isMac()) { - protocolBLEGanglion.setIsActive(false); - protocolBLEGanglion.wasPressed = false; - } - protocolBLED112Ganglion.setIsActive(false); protocolBLED112Ganglion.wasPressed = false; protocolWifiGanglion.setIsActive(false); @@ -1576,30 +1104,6 @@ class ControlPanel { sampleRate500.wasPressed = false; sampleRate1000.setIsActive(false); sampleRate1000.wasPressed = false; - latencyCyton5ms.setIsActive(false); - latencyCyton5ms.wasPressed = false; - latencyCyton10ms.setIsActive(false); - latencyCyton10ms.wasPressed = false; - latencyCyton20ms.setIsActive(false); - latencyCyton20ms.wasPressed = false; - latencyGanglion5ms.setIsActive(false); - latencyGanglion5ms.wasPressed = false; - latencyGanglion10ms.setIsActive(false); - latencyGanglion10ms.wasPressed = false; - latencyGanglion20ms.setIsActive(false); - latencyGanglion20ms.wasPressed = false; - wifiInternetProtocolCytonTCP.setIsActive(false); - wifiInternetProtocolCytonTCP.wasPressed = false; - wifiInternetProtocolCytonUDP.setIsActive(false); - wifiInternetProtocolCytonUDP.wasPressed = false; - wifiInternetProtocolCytonUDPBurst.setIsActive(false); - wifiInternetProtocolCytonUDPBurst.wasPressed = false; - wifiInternetProtocolGanglionTCP.setIsActive(false); - wifiInternetProtocolGanglionTCP.wasPressed = false; - wifiInternetProtocolGanglionUDP.setIsActive(false); - wifiInternetProtocolGanglionUDP.wasPressed = false; - wifiInternetProtocolGanglionUDPBurst.setIsActive(false); - wifiInternetProtocolGanglionUDPBurst.wasPressed = false; synthChanButton4.setIsActive(false); synthChanButton4.wasPressed = false; synthChanButton8.setIsActive(false); @@ -1610,8 +1114,6 @@ class ControlPanel { chanButton16.wasPressed = false; selectPlaybackFile.setIsActive(false); selectPlaybackFile.wasPressed = false; - selectSDFile.setIsActive(false); - selectSDFile.wasPressed = false; sampleDataButton.setIsActive(false); sampleDataButton.wasPressed = false; } @@ -1619,32 +1121,32 @@ class ControlPanel { public void initButtonPressed(){ if (initSystemButton.but_txt == "START SESSION") { - if ((eegDataSource == DATASOURCE_CYTON && cyton.getInterface() == INTERFACE_NONE) || (eegDataSource == DATASOURCE_GANGLION && ganglion.getInterface() == INTERFACE_NONE)) { + if ((eegDataSource == DATASOURCE_CYTON && selectedProtocol == BoardProtocol.NONE) || (eegDataSource == DATASOURCE_GANGLION && selectedProtocol == BoardProtocol.NONE)) { output("No Transfer Protocol selected. Please select your Transfer Protocol and retry system initiation."); initSystemButton.wasPressed = false; initSystemButton.setIsActive(false); return; - } else if (eegDataSource == DATASOURCE_CYTON && cyton.getInterface() == INTERFACE_SERIAL && openBCI_portName == "N/A") { //if data source == normal && if no serial port selected OR no SD setting selected + } else if (eegDataSource == DATASOURCE_CYTON && selectedProtocol == BoardProtocol.SERIAL && openBCI_portName == "N/A") { //if data source == normal && if no serial port selected OR no SD setting selected output("No Serial/COM port selected. Please select your Serial/COM port and retry system initiation."); initSystemButton.wasPressed = false; initSystemButton.setIsActive(false); return; - } else if (eegDataSource == DATASOURCE_CYTON && cyton.getInterface() == INTERFACE_HUB_WIFI && wifi_portName == "N/A" && hub.getWiFiStyle() == WIFI_DYNAMIC) { + } else if (eegDataSource == DATASOURCE_CYTON && selectedProtocol == BoardProtocol.WIFI && wifi_portName == "N/A" && controlPanel.getWifiSearchStyle() == controlPanel.WIFI_DYNAMIC) { output("No Wifi Shield selected. Please select your Wifi Shield and retry system initiation."); initSystemButton.wasPressed = false; initSystemButton.setIsActive(false); return; - } else if (eegDataSource == DATASOURCE_PLAYBACKFILE && playbackData_fname == "N/A") { //if data source == playback && playback file == 'N/A' + } else if (eegDataSource == DATASOURCE_PLAYBACKFILE && playbackData_fname == "N/A" && sdData_fname == "N/A") { //if data source == playback && playback file == 'N/A' output("No playback file selected. Please select a playback file and retry system initiation."); // tell user that they need to select a file before the system can be started initSystemButton.wasPressed = false; initSystemButton.setIsActive(false); return; - } else if (eegDataSource == DATASOURCE_GANGLION && (ganglion.getInterface() == INTERFACE_HUB_BLE || ganglion.getInterface() == INTERFACE_HUB_BLED112) && ganglion_portName == "N/A") { + } else if (eegDataSource == DATASOURCE_GANGLION && (selectedProtocol == BoardProtocol.BLE || selectedProtocol == BoardProtocol.BLED112) && ganglion_portName == "N/A") { output("No BLE device selected. Please select your Ganglion device and retry system initiation."); initSystemButton.wasPressed = false; initSystemButton.setIsActive(false); return; - } else if (eegDataSource == DATASOURCE_GANGLION && ganglion.getInterface() == INTERFACE_HUB_WIFI && wifi_portName == "N/A" && hub.getWiFiStyle() == WIFI_DYNAMIC) { + } else if (eegDataSource == DATASOURCE_GANGLION && selectedProtocol == BoardProtocol.WIFI && wifi_portName == "N/A" && controlPanel.getWifiSearchStyle() == controlPanel.WIFI_DYNAMIC) { output("No Wifi Shield selected. Please select your Wifi Shield and retry system initiation."); initSystemButton.wasPressed = false; initSystemButton.setIsActive(false); @@ -1654,50 +1156,42 @@ public void initButtonPressed(){ initSystemButton.wasPressed = false; initSystemButton.setIsActive(false); return; - } else if (playbackFileIsEmpty) { - outputError("Playback file appears empty. Try loading a different file."); - initSystemButton.wasPressed = false; - initSystemButton.setIsActive(false); - return; } else { //otherwise, initiate system! //verbosePrint("ControlPanel: CPmouseReleased: init"); initSystemButton.setString("STOP SESSION"); // Global steps to START SESSION // Prepare the serial port if (eegDataSource == DATASOURCE_CYTON) { - verbosePrint("ControlPanel — port is open: " + cyton.isPortOpen()); - if (cyton.isPortOpen() == true) { - cyton.closePort(); - } sessionName = cp5.get(Textfield.class, "fileNameCyton").getText(); // store the current text field value of "File Name" to be passed along to dataFiles controlPanel.serialBox.autoConnect.setIgnoreHover(false); //reset the auto-connect button controlPanel.serialBox.autoConnect.setColorNotPressed(255); } else if(eegDataSource == DATASOURCE_GANGLION){ - verbosePrint("ControlPanel — port is open: " + ganglion.isPortOpen()); - if (ganglion.isPortOpen()) { - ganglion.closePort(); - } + // verbosePrint("ControlPanel — port is open: " + ganglion.isPortOpen()); + // if (ganglion.isPortOpen()) { + // ganglion.closePort(); + // } sessionName = cp5.get(Textfield.class, "fileNameGanglion").getText(); // store the current text field value of "File Name" to be passed along to dataFiles } - - if (outputDataSource == OUTPUT_SOURCE_ODF && eegDataSource < DATASOURCE_PLAYBACKFILE) { - settings.setLogFileMaxDuration(); + else { + sessionName = directoryManager.getFileNameDateTime(); } - if (hub.getWiFiStyle() == WIFI_STATIC && (cyton.isWifi() || ganglion.isWifi())) { + if (controlPanel.getWifiSearchStyle() == controlPanel.WIFI_STATIC && (selectedProtocol == BoardProtocol.WIFI || selectedProtocol == BoardProtocol.WIFI)) { wifi_ipAddress = cp5.get(Textfield.class, "staticIPAddress").getText(); println("Static IP address of " + wifi_ipAddress); } + + //Set this flag to true, and draw "Starting Session..." to screen after then next draw() loop midInit = true; - output("Attempting to Start Session..."); + output("Attempting to Start Session..."); // Show this at the bottom of the GUI println("initButtonPressed: Calling initSystem() after next draw()"); } } else { //if system is already active ... stop session and flip button state back outputInfo("Learn how to use this application and more at openbci.github.io/Documentation/"); initSystemButton.setString("START SESSION"); - cp5.get(Textfield.class, "fileNameCyton").setText(getDateString()); //creates new data file name so that you don't accidentally overwrite the old one - cp5.get(Textfield.class, "fileNameGanglion").setText(getDateString()); //creates new data file name so that you don't accidentally overwrite the old one + cp5.get(Textfield.class, "fileNameCyton").setText(directoryManager.getFileNameDateTime()); //creates new data file name so that you don't accidentally overwrite the old one + cp5.get(Textfield.class, "fileNameGanglion").setText(directoryManager.getFileNameDateTime()); //creates new data file name so that you don't accidentally overwrite the old one cp5.get(Textfield.class, "staticIPAddress").setText(wifi_ipAddress); // Fills the last (or default) IP address haltSystem(); } @@ -1707,12 +1201,7 @@ void updateToNChan(int _nchan) { nchan = _nchan; settings.slnchan = _nchan; //used in SoftwareSettings.pde only fftBuff = new FFT[nchan]; //reinitialize the FFT buffer - yLittleBuff_uV = new float[nchan][nPointsPerUpdate]; - println("channel count set to " + str(nchan)); - hub.initDataPackets(_nchan, 3); - ganglion.initDataPackets(_nchan, 3); - cyton.initDataPackets(_nchan, 3); - updateChannelArrays(nchan); //make sure to reinitialize the channel arrays with the right number of channels + println("Channel count set to " + str(nchan)); } //==============================================================================// @@ -1720,59 +1209,29 @@ void updateToNChan(int _nchan) { // CONTROL PANEL BOXes (control widgets) // //==============================================================================// -class NoHubBox { +class DataSourceBox { int x, y, w, h, padding; //size and position + int numItems = 4; + int boxHeight = 24; + int spacing = 43; - NoHubBox(int _x, int _y, int _w, int _h, int _padding) { + DataSourceBox(int _x, int _y, int _w, int _h, int _padding) { + if (novaXREnabled) numItems = 5; x = _x; y = _y; w = _w; - h = 73; + h = spacing + (numItems * boxHeight); padding = _padding; - noHubShowDoc = new Button (x + padding, y + padding*2 + 13, w - padding*2, 24, "OPENBCI GUI INSTALL GUIDE", fontInfo.buttonLabel_size); - noHubShowDoc.setURL("https://openbci.github.io/Documentation/docs/06Software/01-OpenBCISoftware/GUIDocs"); - } - - public void draw() { - pushStyle(); - fill(boxColor); - stroke(boxStrokeColor); - strokeWeight(1); - rect(x, y, w, h); - fill(bgColor); - textFont(h3, 16); - textAlign(LEFT, TOP); - text("HUB NOT CONNECTED", x + padding, y + padding); - noHubShowDoc.draw(); - popStyle(); - } -}; - -class DataSourceBox { - int x, y, w, h, padding; //size and position - int numItems = 4; - int boxHeight = 24; - int spacing = 43; - - - CheckBox sourceCheckBox; - - DataSourceBox(int _x, int _y, int _w, int _h, int _padding) { - x = _x; - y = _y; - w = _w; - h = spacing + (numItems * boxHeight); - padding = _padding; - - sourceList = new MenuList(cp5, "sourceList", w - padding*2, numItems * boxHeight, p4); + sourceList = new MenuList(cp5, "sourceList", w - padding*2, numItems * boxHeight, p3); // sourceList.itemHeight = 28; // sourceList.padding = 9; sourceList.setPosition(x + padding, y + padding*2 + 13); - sourceList.addItem(makeItem("LIVE (from Cyton)")); - sourceList.addItem(makeItem("LIVE (from Ganglion)")); - sourceList.addItem(makeItem("PLAYBACK (from file)")); - sourceList.addItem(makeItem("SYNTHETIC (algorithmic)")); + sourceList.addItem(makeItem("LIVE (from Cyton)", DATASOURCE_CYTON)); + sourceList.addItem(makeItem("LIVE (from Ganglion)", DATASOURCE_GANGLION)); + if (novaXREnabled) sourceList.addItem(makeItem("LIVE (from NovaXR)", DATASOURCE_NOVAXR)); + sourceList.addItem(makeItem("PLAYBACK (from file)", DATASOURCE_PLAYBACKFILE)); + sourceList.addItem(makeItem("SYNTHETIC (algorithmic)", DATASOURCE_SYNTHETIC)); sourceList.scrollerLength = 10; } @@ -1800,7 +1259,7 @@ class DataSourceBox { class SerialBox { int x, y, w, h, padding; //size and position - Button autoConnect; + Button_obci autoConnect; SerialBox(int _x, int _y, int _w, int _h, int _padding) { x = _x; @@ -1809,9 +1268,9 @@ class SerialBox { h = 70; padding = _padding; - autoConnect = new Button(x + padding, y + padding*3 + 4, w - padding*3 - 70, 24, "AUTO-CONNECT", fontInfo.buttonLabel_size); + autoConnect = new Button_obci(x + padding, y + padding*3 + 4, w - padding*3 - 70, 24, "AUTO-CONNECT", fontInfo.buttonLabel_size); autoConnect.setHelpText("Attempt to auto-connect to Cyton. Try \"Manual\" if this does not work."); - popOutRadioConfigButton = new Button(x + w - 70 - padding, y + padding*3 + 4, 70, 24,"Manual >",fontInfo.buttonLabel_size); + popOutRadioConfigButton = new Button_obci(x + w - 70 - padding, y + padding*3 + 4, 70, 24,"Manual >",fontInfo.buttonLabel_size); popOutRadioConfigButton.setHelpText("Having trouble connecting to Cyton? Click here to access Radio Configuration tools."); } @@ -1830,51 +1289,18 @@ class SerialBox { text("SERIAL CONNECT", x + padding, y + padding); popStyle(); - if (cyton.isSerial()) { + if (selectedProtocol == BoardProtocol.SERIAL) { popOutRadioConfigButton.draw(); autoConnect.draw(); } } - - public void attemptAutoConnectCyton() { - //Fetch the number of com ports... - int numComPorts = cp5.get(MenuList.class, "serialList").getListSize(); - String _regex = ""; - //Then look for matching cyton dongle - for (int i = 0; i < numComPorts; i++) { - String comPort = (String)cp5.get(MenuList.class, "serialList").getItem(i).get("headline"); - if (isMac()) { - _regex = "^/dev/tty.usbserial-DM.*$"; - } else if (isWindows()) { - _regex = "COM.*$"; - } else if (isLinux()) { - _regex = "^/dev/ttyUSB.*$"; - } - if (ableToConnect(comPort, _regex)) return; - } //end for loop for all com ports - - } //end attempAutoConnectCyton - - private boolean ableToConnect(String _comPort, String _regex) { - if (systemMode < SYSTEMMODE_POSTINIT) { - //There are quite a few serial ports on Linux, but not many that start with /dev/ttyUSB - String[] foundCytonPort = match(_comPort, _regex); - if (foundCytonPort != null) { // If not null, then a match was found - println("ControlPanel: Attempting to connect to " + _comPort); - openBCI_portName = foundCytonPort[0]; - initButtonPressed(); - if (systemMode == SYSTEMMODE_POSTINIT) return true; - } - return false; - } else { - return true; - } - } }; class ComPortBox { - int x, y, w, h, padding; //size and position - boolean isShowing; + private int x, y, w, h, padding; //size and position + public boolean isShowing; + public MenuList serialList; + RadioConfig cytonRadioCfg; ComPortBox(int _x, int _y, int _w, int _h, int _padding) { x = _x; @@ -1883,19 +1309,15 @@ class ComPortBox { h = 140 + _padding; padding = _padding; isShowing = false; + cytonRadioCfg = new RadioConfig(); - refreshPort = new Button (x + padding, y + padding*4 + 72 + 8, w - padding*2, 24, "REFRESH LIST", fontInfo.buttonLabel_size); - serialList = new MenuList(cp5, "serialList", w - padding*2, 72, p4); - // println(w-padding*2); + refreshPort = new Button_obci (x + padding, y + padding*4 + 72 + 8, w - padding*2, 24, "REFRESH LIST", fontInfo.buttonLabel_size); + serialList = new MenuList(cp5, "serialList", w - padding*2, 72, p3); serialList.setPosition(x + padding, y + padding*3 + 8); - serialPorts = Serial.list(); - for (int i = 0; i < serialPorts.length; i++) { - String tempPort = serialPorts[(serialPorts.length-1) - i]; //list backwards... because usually our port is at the bottom - serialList.addItem(makeItem(tempPort)); - } } public void update() { + serialList.updateMenu(); } public void draw() { @@ -1911,10 +1333,69 @@ class ComPortBox { refreshPort.draw(); popStyle(); } + + public void attemptAutoConnectCyton() { + println("ControlPanel: Attempting to Auto-Connect to Cyton"); + LinkedList comPorts = getCytonComPorts(); + if (!comPorts.isEmpty()) { + openBCI_portName = comPorts.getFirst(); + if (cytonRadioCfg.get_channel()) { + initButtonPressed(); + buttonHelpText.setVisible(false); + } + else { + outputWarn("Found a Cyton dongle, but could not connect to the board."); + } + } + else { + outputWarn("No Cyton dongles were found."); + } + } + + public void refreshPortListCyton(){ + serialList.items.clear(); + + Thread thread = new Thread(){ + public void run(){ + refreshPort.setString("SEARCHING..."); + + LinkedList comPorts = getCytonComPorts(); + for (String comPort : comPorts) { + serialList.addItem(makeItem("(Cyton) " + comPort, comPort, "")); + } + serialList.updateMenu(); + + refreshPort.setString("REFRESH LIST"); + } + }; + + thread.start(); + } + + private LinkedList getCytonComPorts() { + final String[] names = {"FT231X USB UART", "VCP0"}; + final SerialPort[] comPorts = SerialPort.getCommPorts(); + LinkedList results = new LinkedList(); + for (SerialPort comPort : comPorts) { + for (String name : names) { + if (comPort.toString().equals(name)) { + String found = ""; + if (isMac() || isLinux()) found += "/dev/"; + found += comPort.getSystemPortName(); + println("ControlPanel: Found Cyton Dongle on COM port: " + found); + results.add(found); + } + } + } + + return results; + } + }; class BLEBox { - int x, y, w, h, padding; //size and position + private int x, y, w, h, padding; //size and position + private volatile boolean bleIsRefreshing = false; BLEBox(int _x, int _y, int _w, int _h, int _padding) { x = _x; @@ -1922,9 +1403,8 @@ class BLEBox { w = _w; h = 140 + _padding; padding = _padding; - - refreshBLE = new Button (x + padding, y + padding*4 + 72 + 8, w - padding*5, 24, "START SEARCH", fontInfo.buttonLabel_size); - bleList = new MenuList(cp5, "bleList", w - padding*2, 72, p4); + refreshBLE = new Button_obci (x + padding, y + padding*4 + 72 + 8, w - padding*5, 24, "START SEARCH", fontInfo.buttonLabel_size); + bleList = new MenuList(cp5, "bleList", w - padding*2, 72, p3); bleList.setPosition(x + padding, y + padding*3 + 8); } @@ -1943,44 +1423,95 @@ class BLEBox { text("BLE DEVICES", x + padding, y + padding); popStyle(); - refreshBLE.draw(); - - if(isHubInitialized && isHubObjectInitialized && ganglion.isBLE() && hub.isSearching()){ - image(loadingGIF_blue, w + 225, y + padding*4 + 72 + 10, 20, 20); - refreshBLE.setString("SEARCHING..."); + if (bleIsRefreshing) { + //Display spinning cog gif + image(loadingGIF_blue, w + 225, refreshBLE.but_y + 4, 20, 20); } else { - refreshBLE.setString("START SEARCH"); + //Draw small grey circle + pushStyle(); + fill(#999999); + ellipseMode(CENTER); + ellipse(w + 225 + 10, refreshBLE.but_y + 12, 12, 12); + popStyle(); } + + refreshBLE.draw(); + } + + public void mousePressed() { + } - public void refreshBLEList() { + private void refreshGanglionBLEList() { + if (bleIsRefreshing) { + output("BLE Devices Refreshing in progress"); + return; + } + output("BLE Devices Refreshing"); bleList.items.clear(); - for (int i = 0; i < hub.deviceList.length; i++) { - String tempPort = hub.deviceList[i]; - bleList.addItem(makeItem(tempPort)); + + Thread thread = new Thread(){ + public void run(){ + refreshBLE.setString("SEARCHING..."); + bleIsRefreshing = true; + final String comPort = getBLED112Port(); + if (comPort != null) { + try { + BLEMACAddrMap = GUIHelper.scan_for_ganglions (comPort, 3); + for (Map.Entry entry : BLEMACAddrMap.entrySet ()) + { + bleList.addItem(makeItem(entry.getKey(), comPort, "")); + bleList.updateMenu(); + } + } catch (GanglionError e) + { + e.printStackTrace(); + } + } else { + outputError("No BLED112 Dongle Found"); + } + refreshBLE.setString("START SEARCH"); + bleIsRefreshing = false; + } + }; + + thread.start(); + } + + public String getBLED112Port() { + String name = "Low Energy Dongle"; + SerialPort[] comPorts = SerialPort.getCommPorts(); + for (int i = 0; i < comPorts.length; i++) { + if (comPorts[i].toString().equals(name)) { + String found = ""; + if (isMac() || isLinux()) found += "/dev/"; + found += comPorts[i].getSystemPortName().toString(); + println("ControlPanel: Found BLED112 Dongle on COM port: " + found); + return found; + } } - bleList.updateMenu(); + return null; } }; class WifiBox { - int x, y, w, h, padding; //size and position + private int x, y, w, h, padding; //size and position + private boolean wifiIsRefreshing = false; WifiBox(int _x, int _y, int _w, int _h, int _padding) { x = _x; y = _y; w = _w; - h = 184 + _padding; + h = 184 + _padding + 14; padding = _padding; - wifiIPAddressDynamic = new Button (x + padding, y + padding*2 + 30, (w-padding*3)/2, 24, "DYNAMIC IP", fontInfo.buttonLabel_size); - if (hub.getWiFiStyle() == WIFI_DYNAMIC) wifiIPAddressDynamic.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - wifiIPAddressStatic = new Button (x + padding*2 + (w-padding*3)/2, y + padding*2 + 30, (w-padding*3)/2, 24, "STATIC IP", fontInfo.buttonLabel_size); - if (hub.getWiFiStyle() == WIFI_STATIC) wifiIPAddressStatic.setColorNotPressed(isSelected_color); //make it appear like this one is already selected + wifiIPAddressDynamic = new Button_obci (x + padding, y + padding*2 + 30, (w-padding*3)/2, 24, "DYNAMIC IP", fontInfo.buttonLabel_size); + wifiIPAddressDynamic.setColorNotPressed(isSelected_color); //make it appear like this one is already selected + wifiIPAddressStatic = new Button_obci (x + padding*2 + (w-padding*3)/2, y + padding*2 + 30, (w-padding*3)/2, 24, "STATIC IP", fontInfo.buttonLabel_size); + wifiIPAddressStatic.setColorNotPressed(colorNotPressed); - refreshWifi = new Button (x + padding, y + padding*5 + 72 + 8 + 24, w - padding*5, 24, "START SEARCH", fontInfo.buttonLabel_size); - wifiList = new MenuList(cp5, "wifiList", w - padding*2, 72 + 8, p4); - popOutWifiConfigButton = new Button(x+padding + (w-padding*4), y + padding, 20,20,">",fontInfo.buttonLabel_size); + refreshWifi = new Button_obci (x + padding, y + padding*5 + 72 + 8 + 24, w - padding*5, 24, "START SEARCH", fontInfo.buttonLabel_size); + wifiList = new MenuList(cp5, "wifiList", w - padding*2, 72 + 8, p3); wifiList.setPosition(x + padding, y + padding*4 + 8 + 24); // Call to update the list @@ -2023,10 +1554,7 @@ class WifiBox { popStyle(); - popOutWifiConfigButton.but_y = y + padding; - popOutWifiConfigButton.draw(); - - if (hub.getWiFiStyle() == WIFI_STATIC) { + if (controlPanel.getWifiSearchStyle() == controlPanel.WIFI_STATIC) { pushStyle(); fill(bgColor); textFont(h3, 16); @@ -2039,12 +1567,21 @@ class WifiBox { refreshWifi.draw(); refreshWifi.but_y = y + h - padding - 24; - if(isHubInitialized && isHubObjectInitialized && (ganglion.isWifi() || cyton.isWifi()) && hub.isSearching()){ + + String boardIpInfo = "BOARD IP: "; + if (wifi_portName != "N/A") { // If user has selected a board from the menulist... + boardIpInfo += wifi_ipAddress; + } + fill(bgColor); + textFont(h3, 16); + textAlign(LEFT, TOP); + text(boardIpInfo, x + w/2 - textWidth(boardIpInfo)/2, y + h - padding - 46); + + if (wifiIsRefreshing){ + //Display spinning cog gif image(loadingGIF_blue, w + 225, refreshWifi.but_y + 4, 20, 20); - refreshWifi.setString("SEARCHING..."); } else { - refreshWifi.setString("START SEARCH"); - + //Draw small grey circle pushStyle(); fill(#999999); ellipseMode(CENTER); @@ -2055,15 +1592,30 @@ class WifiBox { } public void refreshWifiList() { - println("refreshWifiList"); + output("Wifi Devices Refreshing"); wifiList.items.clear(); - if (hub.deviceList != null) { - for (int i = 0; i < hub.deviceList.length; i++) { - String tempPort = hub.deviceList[i]; - wifiList.addItem(makeItem(tempPort)); + Thread thread = new Thread(){ + public void run() { + refreshWifi.setString("SEARCHING..."); + wifiIsRefreshing = true; + try { + List devices = SSDPClient.discover (3000, "urn:schemas-upnp-org:device:Basic:1"); + if (devices.isEmpty ()) { + println("No WIFI Shields found"); + } + for (int i = 0; i < devices.size(); i++) { + wifiList.addItem(makeItem(devices.get(i).getName(), devices.get(i).getIPAddress(), "")); + } + wifiList.updateMenu(); + } catch (Exception e) { + println("Exception in wifi shield scanning"); + e.printStackTrace (); + } + refreshWifi.setString("START SEARCH"); + wifiIsRefreshing = false; } - } - wifiList.updateMenu(); + }; + thread.start(); } }; @@ -2077,8 +1629,8 @@ class InterfaceBoxCyton { h = (24 + _padding) * 3; padding = _padding; - protocolSerialCyton = new Button (x + padding, y + padding * 3 + 4, w - padding * 2, 24, "Serial (from Dongle)", fontInfo.buttonLabel_size); - protocolWifiCyton = new Button (x + padding, y + padding * 4 + 24 + 4, w - padding * 2, 24, "Wifi (from Wifi Shield)", fontInfo.buttonLabel_size); + protocolSerialCyton = new Button_obci (x + padding, y + padding * 3 + 4, w - padding * 2, 24, "Serial (from Dongle)", fontInfo.buttonLabel_size); + protocolWifiCyton = new Button_obci (x + padding, y + padding * 4 + 24 + 4, w - padding * 2, 24, "Wifi (from Wifi Shield)", fontInfo.buttonLabel_size); } public void update() {} @@ -2112,16 +1664,9 @@ class InterfaceBoxGanglion { int buttonHeight = 24; int paddingCount = 1; - if (isMac()) { - protocolBLEGanglion = new Button (x + padding, y + padding * paddingCount + buttonHeight, w - padding * 2, 24, "Bluetooth (Built In)", fontInfo.buttonLabel_size); - paddingCount ++; - // Fix height for extra button - h += padding + buttonHeight; - } - - protocolBLED112Ganglion = new Button (x + padding, y + padding * paddingCount + buttonHeight * paddingCount, w - padding * 2, 24, "Bluetooth (BLED112 Dongle)", fontInfo.buttonLabel_size); + protocolBLED112Ganglion = new Button_obci (x + padding, y + padding * paddingCount + buttonHeight * paddingCount, w - padding * 2, 24, "Bluetooth (BLED112 Dongle)", fontInfo.buttonLabel_size); paddingCount ++; - protocolWifiGanglion = new Button (x + padding, y + padding * paddingCount + buttonHeight * paddingCount, w - padding * 2, 24, "Wifi (from Wifi Shield)", fontInfo.buttonLabel_size); + protocolWifiGanglion = new Button_obci (x + padding, y + padding * paddingCount + buttonHeight * paddingCount, w - padding * 2, 24, "Wifi (from Wifi Shield)", fontInfo.buttonLabel_size); paddingCount ++; } @@ -2138,10 +1683,7 @@ class InterfaceBoxGanglion { textAlign(LEFT, TOP); text("PICK TRANSFER PROTOCOL", x + padding, y + padding); popStyle(); - - if (isMac()) { - protocolBLEGanglion.draw(); - } + protocolWifiGanglion.draw(); protocolBLED112Ganglion.draw(); } @@ -2158,7 +1700,6 @@ class SessionDataBox { int maxDurTextWidth = 82; int maxDurText_x = 0; String maxDurDropdownName; - boolean dropdownWasClicked = false; SessionDataBox (int _x, int _y, int _w, int _h, int _padding, int _dataSource) { odfModeHeight = bdfModeHeight + 24 + _padding; @@ -2171,13 +1712,13 @@ class SessionDataBox { maxDurTextWidth += padding*5 + 1; //button to autogenerate file name based on time/date - autoSessionName = new Button (x + padding, y + 66, w-(padding*2), 24, "GENERATE SESSION NAME", fontInfo.buttonLabel_size); + autoSessionName = new Button_obci (x + padding, y + 66, w-(padding*2), 24, "GENERATE SESSION NAME", fontInfo.buttonLabel_size); autoSessionName.setHelpText("Autogenerate a session name based on the date and time."); - outputODF = new Button (x + padding, y + padding*2 + 18 + 58, (w-padding*3)/2, 24, "OpenBCI", fontInfo.buttonLabel_size); + outputODF = new Button_obci (x + padding, y + padding*2 + 18 + 58, (w-padding*3)/2, 24, "OpenBCI", fontInfo.buttonLabel_size); outputODF.setHelpText("Set GUI data output to OpenBCI Data Format (.txt). A new file will be made in the session folder when the data stream is paused or max file duration is reached."); //Output source is ODF by default if (outputDataSource == OUTPUT_SOURCE_ODF) outputODF.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - outputBDF = new Button (x + padding*2 + (w-padding*3)/2, y + padding*2 + 18 + 58, (w-padding*3)/2, 24, "BDF+", fontInfo.buttonLabel_size); + outputBDF = new Button_obci (x + padding*2 + (w-padding*3)/2, y + padding*2 + 18 + 58, (w-padding*3)/2, 24, "BDF+", fontInfo.buttonLabel_size); outputBDF.setHelpText("Set GUI data output to BioSemi Data Format (.bdf). All session data is contained in one .bdf file. View using an EDF/BDF browser."); if (outputDataSource == OUTPUT_SOURCE_BDF) outputBDF.setColorNotPressed(isSelected_color); //make it appear like this one is already selected @@ -2195,14 +1736,14 @@ class SessionDataBox { .setColorForeground(isSelected_color) // border color when not selected .setColorActive(isSelected_color) // border color when selected .setColorCursor(color(26, 26, 26)) - .setText(getDateString()) + .setText(directoryManager.getFileNameDateTime()) .align(5, 10, 20, 40) .onDoublePress(cb) .setAutoClear(true); //The OpenBCI data format max duration dropdown is controlled by the local cp5 instance cp5_dataLog_dropdown = new ControlP5(ourApplet); - maxDurDropdownName = (_dataSource == DATASOURCE_CYTON) ? "maxFileDurationCyton" : "maxFileDurationGanglion"; + maxDurDropdownName = "maxFileDuration"; createDropdown(maxDurDropdownName, Arrays.asList(settings.fileDurations)); cp5_dataLog_dropdown.setGraphics(ourApplet, 0,0); cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).setPosition(x + maxDurTextWidth, outputODF.but_y + 24 + padding); @@ -2211,7 +1752,7 @@ class SessionDataBox { } public void update() { - openCloseDropdown(); + } public void draw() { @@ -2223,7 +1764,7 @@ class SessionDataBox { fill(bgColor); textFont(h3, 16); textAlign(LEFT, TOP); - text("Session Data", x + padding, y + padding); + text("SESSION DATA", x + padding, y + padding); textFont(p4, 14); text("Name", x + padding, y + padding*2 + 14); popStyle(); @@ -2237,23 +1778,28 @@ class SessionDataBox { if (outputDataSource == OUTPUT_SOURCE_ODF) { pushStyle(); //draw backgrounds to dropdown scrollableLists ... unfortunately ControlP5 doesn't have this by default, so we have to hack it to make it look nice... + //Dropdown is drawn at the end of ControlPanel.draw() fill(bgColor); - rect(cp5_dataLog_dropdown.getController(maxDurDropdownName).getPosition()[0]-1, cp5_dataLog_dropdown.getController(maxDurDropdownName).getPosition()[1]-1, cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).getWidth()+2, cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).getHeight()+2); + cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).setVisible(true); + cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).setPosition(x + maxDurTextWidth, outputODF.but_y + 24 + padding); + //Carefully draw some text to the left of above dropdown, otherwise this text moves when changing WiFi mode + int extraPadding = (controlPanel.getWifiSearchStyle() == controlPanel.WIFI_STATIC) || selectedProtocol != BoardProtocol.WIFI + ? 20 + : 5; fill(bgColor); textFont(p4, 14); - text("Max File Duration", maxDurText_x, outputODF.but_y + outputODF.but_dy + padding*3 - 3); + text("Max File Duration", maxDurText_x, y + h - 24 - padding + extraPadding); popStyle(); - cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).setVisible(true); - cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).setPosition(x + maxDurTextWidth, outputODF.but_y + 24 + padding); - //Dropdown is drawn at the end of ControlPanel.draw() + } } void createDropdown(String name, List _items){ - cp5_dataLog_dropdown.addScrollableList(name) + ScrollableList scrollList = new CustomScrollableList(cp5_dataLog_dropdown, name) .setOpen(false) .setColor(settings.dropdownColors) + .setBackgroundColor(150) /* .setColorBackground(color(31,69,110)) // text field bg color .setColorValueLabel(color(0)) // text color @@ -2287,6 +1833,20 @@ class SessionDataBox { .getStyle() //need to grab style before affecting the paddingTop .setPaddingTop(3) //4-pixel vertical offset to center text ; + + scrollList.onEnter(new CallbackListener() { + public void controlEvent(CallbackEvent event) { + lockElements(true); + } + }); + + scrollList.onLeave(new CallbackListener() { + public void controlEvent(CallbackEvent event) { + ScrollableList theList = (ScrollableList)(event.getController()); + lockElements(theList.isOpen()); + } + }); + } //Returns: 0 for Cyton, 1 for Ganglion @@ -2302,46 +1862,12 @@ class SessionDataBox { h = bdfModeHeight; } - private void openCloseDropdown() { - //Close the dropdown if it is open and mouse is no longer over it - if (cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).isOpen()){ - if (!cp5_dataLog_dropdown.getController(maxDurDropdownName).isMouseOver()){ - //println("----Closing dropdown " + maxDurDropdownName); - cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).close(); - lockElements(false); - } - - } - // Open the dropdown if it's not open, but not if it was recently clicked - // Makes sure dropdown stays closed after user selects an option - if (!dropdownWasClicked) { - if (!cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).isOpen()){ - if (cp5_dataLog_dropdown.getController(maxDurDropdownName).isMouseOver()){ - //println("++++Opening dropdown " + maxDurDropdownName); - cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).open(); - lockElements(true); - } - } - } else { - // This flag is used to gate opening/closing the dropdown - dropdownWasClicked = false; - } - } - // True locks elements, False unlocks elements void lockElements (boolean _toggle) { if (eegDataSource == DATASOURCE_CYTON) { //Cyton for Serial and WiFi (WiFi details are drawn to the right, so no need to lock) chanButton8.setIgnoreHover(_toggle); chanButton16.setIgnoreHover(_toggle); - /* - if (_toggle) { - cp5.get(MenuList.class, "sdTimes").lock(); - } else { - cp5.get(MenuList.class, "sdTimes").unlock(); - } - cp5.get(MenuList.class, "sdTimes").setUpdate(!_toggle); - */ if (_toggle) { controlPanel.sdBox.cp5_sdBox.get(ScrollableList.class, controlPanel.sdBox.sdBoxDropdownName).lock(); } else { @@ -2350,35 +1876,11 @@ class SessionDataBox { controlPanel.sdBox.cp5_sdBox.get(ScrollableList.class, controlPanel.sdBox.sdBoxDropdownName).setUpdate(!_toggle); } else { //Ganglion + Wifi - latencyGanglion5ms.setIgnoreHover(_toggle); - latencyGanglion10ms.setIgnoreHover(_toggle); - latencyGanglion20ms.setIgnoreHover(_toggle); sampleRate200.setIgnoreHover(_toggle); sampleRate1600.setIgnoreHover(_toggle); } } - - void closeDropdown() { - cp5_dataLog_dropdown.get(ScrollableList.class, maxDurDropdownName).close(); - dropdownWasClicked = true; - lockElements(false); - //println("---- DROPDOWN CLICKED -> CLOSING DROPDOWN"); - } }; -////////////////////////////////////////////////////////////// -// Global functions used by the above SessionDataBox dropdowns -void maxFileDurationCyton (int n) { - settings.cytonOBCIMaxFileSize = n; - controlPanel.dataLogBoxCyton.closeDropdown(); - println("ControlPanel: Cyton Max Recording Duration: " + settings.fileDurations[n]); -} - -void maxFileDurationGanglion (int n) { - settings.ganglionOBCIMaxFileSize = n; - controlPanel.dataLogBoxGanglion.closeDropdown(); - println("ControlPanel: Ganglion Max Recording Duration: " + settings.fileDurations[n]); -} -////////////////////////////////////////////////////////////// class ChannelCountBox { int x, y, w, h, padding; //size and position @@ -2391,9 +1893,9 @@ class ChannelCountBox { h = 73; padding = _padding; - chanButton8 = new Button (x + padding, y + padding*2 + 18, (w-padding*3)/2, 24, "8 CHANNELS", fontInfo.buttonLabel_size); + chanButton8 = new Button_obci (x + padding, y + padding*2 + 18, (w-padding*3)/2, 24, "8 CHANNELS", fontInfo.buttonLabel_size); if (nchan == 8) chanButton8.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - chanButton16 = new Button (x + padding*2 + (w-padding*3)/2, y + padding*2 + 18, (w-padding*3)/2, 24, "16 CHANNELS", fontInfo.buttonLabel_size); + chanButton16 = new Button_obci (x + padding*2 + (w-padding*3)/2, y + padding*2 + 18, (w-padding*3)/2, 24, "16 CHANNELS", fontInfo.buttonLabel_size); if (nchan == 16) chanButton16.setColorNotPressed(isSelected_color); //make it appear like this one is already selected } @@ -2433,8 +1935,8 @@ class SampleRateGanglionBox { h = 73; padding = _padding; - sampleRate200 = new Button (x + padding, y + padding*2 + 18, (w-padding*3)/2, 24, "200Hz", fontInfo.buttonLabel_size); - sampleRate1600 = new Button (x + padding*2 + (w-padding*3)/2, y + padding*2 + 18, (w-padding*3)/2, 24, "1600Hz", fontInfo.buttonLabel_size); + sampleRate200 = new Button_obci (x + padding, y + padding*2 + 18, (w-padding*3)/2, 24, "200Hz", fontInfo.buttonLabel_size); + sampleRate1600 = new Button_obci (x + padding*2 + (w-padding*3)/2, y + padding*2 + 18, (w-padding*3)/2, 24, "1600Hz", fontInfo.buttonLabel_size); sampleRate1600.setColorNotPressed(isSelected_color); //make it appear like this one is already selected } @@ -2454,7 +1956,6 @@ class SampleRateGanglionBox { fill(bgColor); //set color to green textFont(h3, 16); textAlign(LEFT, TOP); - text(" " + str((int)ganglion.getSampleRate()) + "Hz", x + padding + 142, y + padding); // print the channel count in green next to the box title popStyle(); sampleRate200.draw(); @@ -2474,9 +1975,9 @@ class SampleRateCytonBox { h = 73; padding = _padding; - sampleRate250 = new Button (x + padding, y + padding*2 + 18, (w-padding*4)/3, 24, "250Hz", fontInfo.buttonLabel_size); - sampleRate500 = new Button (x + padding*2 + (w-padding*4)/3, y + padding*2 + 18, (w-padding*4)/3, 24, "500Hz", fontInfo.buttonLabel_size); - sampleRate1000 = new Button (x + padding*3 + ((w-padding*4)/3)*2, y + padding*2 + 18, (w-padding*4)/3, 24, "1000Hz", fontInfo.buttonLabel_size); + sampleRate250 = new Button_obci (x + padding, y + padding*2 + 18, (w-padding*4)/3, 24, "250Hz", fontInfo.buttonLabel_size); + sampleRate500 = new Button_obci (x + padding*2 + (w-padding*4)/3, y + padding*2 + 18, (w-padding*4)/3, 24, "500Hz", fontInfo.buttonLabel_size); + sampleRate1000 = new Button_obci (x + padding*3 + ((w-padding*4)/3)*2, y + padding*2 + 18, (w-padding*4)/3, 24, "1000Hz", fontInfo.buttonLabel_size); sampleRate1000.setColorNotPressed(isSelected_color); //make it appear like this one is already selected } @@ -2496,7 +1997,6 @@ class SampleRateCytonBox { fill(bgColor); //set color to green textFont(h3, 16); textAlign(LEFT, TOP); - text(" " + str((int)cyton.getSampleRate()) + "Hz", x + padding + 142, y + padding); // print the channel count in green next to the box title popStyle(); sampleRate250.draw(); @@ -2508,206 +2008,6 @@ class SampleRateCytonBox { } }; -class LatencyGanglionBox { - int x, y, w, h, padding; //size and position - - LatencyGanglionBox(int _x, int _y, int _w, int _h, int _padding) { - x = _x; - y = _y; - w = _w; - h = 73; - padding = _padding; - - latencyGanglion5ms = new Button (x + padding, y + padding*2 + 18, (w-padding*4)/3, 24, "5ms", fontInfo.buttonLabel_size); - if (hub.getLatency() == LATENCY_5_MS) latencyGanglion5ms.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - latencyGanglion10ms = new Button (x + padding*2 + (w-padding*4)/3, y + padding*2 + 18, (w-padding*4)/3, 24, "10ms", fontInfo.buttonLabel_size); - if (hub.getLatency() == LATENCY_10_MS) latencyGanglion10ms.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - latencyGanglion20ms = new Button (x + padding*3 + ((w-padding*4)/3)*2, y + padding*2 + 18, (w-padding*4)/3, 24, "20ms", fontInfo.buttonLabel_size); - if (hub.getLatency() == LATENCY_20_MS) latencyGanglion20ms.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - } - - public void update() { - } - - public void draw() { - pushStyle(); - fill(boxColor); - stroke(boxStrokeColor); - strokeWeight(1); - rect(x, y, w, h); - fill(bgColor); - textFont(h3, 16); - textAlign(LEFT, TOP); - text("LATENCY ", x + padding, y + padding); - fill(bgColor); //set color to green - textFont(h3, 16); - textAlign(LEFT, TOP); - text(" " + str(hub.getLatency()/1000) + "ms", x + padding + 142, y + padding); // print the channel count in green next to the box title - popStyle(); - - latencyGanglion5ms.draw(); - latencyGanglion10ms.draw(); - latencyGanglion20ms.draw(); - latencyGanglion5ms.but_y = y + padding*2 + 18; - latencyGanglion10ms.but_y = latencyGanglion5ms.but_y; - latencyGanglion20ms.but_y = latencyGanglion5ms.but_y; - } -}; - -class LatencyCytonBox { - int x, y, w, h, padding; //size and position - - LatencyCytonBox(int _x, int _y, int _w, int _h, int _padding) { - x = _x; - y = _y; - w = _w; - h = 73; - padding = _padding; - - latencyCyton5ms = new Button (x + padding, y + padding*2 + 18, (w-padding*4)/3, 24, "5ms", fontInfo.buttonLabel_size); - if (hub.getLatency() == LATENCY_5_MS) latencyCyton5ms.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - latencyCyton10ms = new Button (x + padding*2 + (w-padding*4)/3, y + padding*2 + 18, (w-padding*4)/3, 24, "10ms", fontInfo.buttonLabel_size); - if (hub.getLatency() == LATENCY_10_MS) latencyCyton10ms.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - latencyCyton20ms = new Button (x + padding*3 + ((w-padding*4)/3)*2, y + padding*2 + 18, (w-padding*4)/3, 24, "20ms", fontInfo.buttonLabel_size); - if (hub.getLatency() == LATENCY_20_MS) latencyCyton20ms.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - } - - public void update() { - } - - public void draw() { - pushStyle(); - fill(boxColor); - stroke(boxStrokeColor); - strokeWeight(1); - rect(x, y, w, h); - fill(bgColor); - textFont(h3, 16); - textAlign(LEFT, TOP); - text("LATENCY ", x + padding, y + padding); - fill(bgColor); //set color to green - textFont(h3, 16); - textAlign(LEFT, TOP); - text(" " + str(hub.getLatency()/1000) + "ms", x + padding + 142, y + padding); // print the channel count in green next to the box title - popStyle(); - - latencyCyton5ms.draw(); - latencyCyton10ms.draw(); - latencyCyton20ms.draw(); - latencyCyton5ms.but_y = y + padding*2 + 18; - latencyCyton10ms.but_y = latencyCyton5ms.but_y; - latencyCyton20ms.but_y = latencyCyton5ms.but_y; - } -}; - -class WifiTransferProtcolGanglionBox { - int x, y, w, h, padding; //size and position - - WifiTransferProtcolGanglionBox(int _x, int _y, int _w, int _h, int _padding) { - x = _x; - y = _y; - w = _w; - h = 73; - padding = _padding; - - wifiInternetProtocolGanglionTCP = new Button (x + padding, y + padding*2 + 18, (w-padding*4)/3, 24, "TCP", fontInfo.buttonLabel_size); - if (hub.getWifiInternetProtocol().equals(TCP)) wifiInternetProtocolGanglionTCP.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - wifiInternetProtocolGanglionUDP = new Button (x + padding*2 + (w-padding*4)/3, y + padding*2 + 18, (w-padding*4)/3, 24, "UDP", fontInfo.buttonLabel_size); - if (hub.getWifiInternetProtocol().equals(UDP)) wifiInternetProtocolGanglionUDP.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - wifiInternetProtocolGanglionUDPBurst = new Button (x + padding*3 + ((w-padding*4)/3)*2, y + padding*2 + 18, (w-padding*4)/3, 24, "UDPx3", fontInfo.buttonLabel_size); - if (hub.getWifiInternetProtocol().equals(UDP_BURST)) wifiInternetProtocolGanglionUDPBurst.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - } - - public void update() { - } - - public void draw() { - pushStyle(); - fill(boxColor); - stroke(boxStrokeColor); - strokeWeight(1); - rect(x, y, w, h); - fill(bgColor); - textFont(h3, 16); - textAlign(LEFT, TOP); - text("WiFi Transfer Protocol ", x + padding, y + padding); - fill(bgColor); //set color to green - textFont(h3, 16); - textAlign(LEFT, TOP); - String dispText; - if (hub.getWifiInternetProtocol().equals(TCP)) { - dispText = "TCP"; - } else if (hub.getWifiInternetProtocol().equals(UDP)) { - dispText = "UDP"; - } else { - dispText = "UDPx3"; - } - text(dispText, x + padding + 184, y + padding); // print the channel count in green next to the box title - popStyle(); - - wifiInternetProtocolGanglionTCP.draw(); - wifiInternetProtocolGanglionUDP.draw(); - wifiInternetProtocolGanglionUDPBurst.draw(); - wifiInternetProtocolGanglionTCP.but_y = y + padding*2 + 18; - wifiInternetProtocolGanglionUDP.but_y = wifiInternetProtocolGanglionTCP.but_y; - wifiInternetProtocolGanglionUDPBurst.but_y = wifiInternetProtocolGanglionTCP.but_y; - } -}; - -class WifiTransferProtcolCytonBox { - int x, y, w, h, padding; //size and position - - WifiTransferProtcolCytonBox(int _x, int _y, int _w, int _h, int _padding) { - x = _x; - y = _y; - w = _w; - h = 73; - padding = _padding; - - wifiInternetProtocolCytonTCP = new Button (x + padding, y + padding*2 + 18, (w-padding*4)/3, 24, "TCP", fontInfo.buttonLabel_size); - if (hub.getWifiInternetProtocol().equals(TCP)) wifiInternetProtocolCytonTCP.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - wifiInternetProtocolCytonUDP = new Button (x + padding*2 + (w-padding*4)/3, y + padding*2 + 18, (w-padding*4)/3, 24, "UDP", fontInfo.buttonLabel_size); - if (hub.getWifiInternetProtocol().equals(UDP)) wifiInternetProtocolCytonUDP.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - wifiInternetProtocolCytonUDPBurst = new Button (x + padding*3 + ((w-padding*4)/3)*2, y + padding*2 + 18, (w-padding*4)/3, 24, "UDPx3", fontInfo.buttonLabel_size); - if (hub.getWifiInternetProtocol().equals(UDP_BURST)) wifiInternetProtocolCytonUDPBurst.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - } - - public void update() { - } - - public void draw() { - pushStyle(); - fill(boxColor); - stroke(boxStrokeColor); - strokeWeight(1); - rect(x, y, w, h); - fill(bgColor); - textFont(h3, 16); - textAlign(LEFT, TOP); - text("WiFi Transfer Protocol ", x + padding, y + padding); - fill(bgColor); //set color to green - textFont(h3, 16); - textAlign(LEFT, TOP); - String dispText; - if (hub.getWifiInternetProtocol().equals(TCP)) { - dispText = "TCP"; - } else if (hub.getWifiInternetProtocol().equals(UDP)) { - dispText = "UDP"; - } else { - dispText = "UDPx3"; - } - text(dispText, x + padding + 184, y + padding); // print the channel count in green next to the box title - popStyle(); - - wifiInternetProtocolCytonTCP.draw(); - wifiInternetProtocolCytonUDP.draw(); - wifiInternetProtocolCytonUDPBurst.draw(); - wifiInternetProtocolCytonTCP.but_y = y + padding*2 + 18; - wifiInternetProtocolCytonUDP.but_y = wifiInternetProtocolCytonTCP.but_y; - wifiInternetProtocolCytonUDPBurst.but_y = wifiInternetProtocolCytonTCP.but_y; - } -}; - class SyntheticChannelCountBox { int x, y, w, h, padding; //size and position @@ -2718,11 +2018,11 @@ class SyntheticChannelCountBox { h = 73; padding = _padding; - synthChanButton4 = new Button (x + padding, y + padding*2 + 18, (w-padding*4)/3, 24, "4 chan", fontInfo.buttonLabel_size); + synthChanButton4 = new Button_obci (x + padding, y + padding*2 + 18, (w-padding*4)/3, 24, "4 chan", fontInfo.buttonLabel_size); if (nchan == 4) synthChanButton4.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - synthChanButton8 = new Button (x + padding*2 + (w-padding*4)/3, y + padding*2 + 18, (w-padding*4)/3, 24, "8 chan", fontInfo.buttonLabel_size); + synthChanButton8 = new Button_obci (x + padding*2 + (w-padding*4)/3, y + padding*2 + 18, (w-padding*4)/3, 24, "8 chan", fontInfo.buttonLabel_size); if (nchan == 8) synthChanButton8.setColorNotPressed(isSelected_color); //make it appear like this one is already selected - synthChanButton16 = new Button (x + padding*3 + ((w-padding*4)/3)*2, y + padding*2 + 18, (w-padding*4)/3, 24, "16 chan", fontInfo.buttonLabel_size); + synthChanButton16 = new Button_obci (x + padding*3 + ((w-padding*4)/3)*2, y + padding*2 + 18, (w-padding*4)/3, 24, "16 chan", fontInfo.buttonLabel_size); if (nchan == 16) synthChanButton16.setColorNotPressed(isSelected_color); //make it appear like this one is already selected } @@ -2752,28 +2052,30 @@ class SyntheticChannelCountBox { }; class RecentPlaybackBox { - int x, y, w, h, padding; //size and position - StringList shortFileNames = new StringList(); - StringList longFilePaths = new StringList(); + private int x, y, w, h, padding; //size and position + private StringList shortFileNames = new StringList(); + private StringList longFilePaths = new StringList(); private String filePickedShort = "Select Recent Playback File"; - ControlP5 cp5_recentPlayback_dropdown; + private ControlP5 cp5_recentPlayback_dropdown; + private int titleH = 14; + private int buttonH = 24; RecentPlaybackBox(int _x, int _y, int _w, int _h, int _padding) { x = _x; y = _y; w = _w; - h = 67; + h = titleH + buttonH + _padding*3; padding = _padding; cp5_recentPlayback_dropdown = new ControlP5(ourApplet); + cp5_recentPlayback_dropdown.setAutoDraw(false); getRecentPlaybackFiles(); String[] temp = shortFileNames.array(); createDropdown("recentFiles", Arrays.asList(temp)); cp5_recentPlayback_dropdown.setGraphics(ourApplet, 0,0); cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").setPosition(x + padding, y + padding*2 + 13); - cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").setSize(w - padding*2, (temp.length + 1) * 24); - cp5_recentPlayback_dropdown.setAutoDraw(false); + cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").setSize(w - padding*2, (temp.length + 1) * buttonH); } /////*Update occurs while control panel is open*///// @@ -2784,7 +2086,7 @@ class RecentPlaybackBox { getRecentPlaybackFiles(); String[] temp = shortFileNames.array(); cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").addItems(temp); - cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").setSize(w - padding*2, (temp.length + 1) * 24); + cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").setSize(w - padding*2, (temp.length + 1) * buttonH); } } @@ -2801,13 +2103,12 @@ class RecentPlaybackBox { fill(boxColor); stroke(boxStrokeColor); strokeWeight(1); - rect(x, y, w, h + cp5_recentPlayback_dropdown.getController("recentFiles").getHeight() - padding*2); + rect(x, y, w, h + cp5_recentPlayback_dropdown.getController("recentFiles").getHeight() - padding*2.5); fill(bgColor); textFont(h3, 16); textAlign(LEFT, TOP); text("PLAYBACK HISTORY", x + padding, y + padding); popStyle(); - cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").setVisible(true); cp5_recentPlayback_dropdown.draw(); } @@ -2822,8 +2123,8 @@ class RecentPlaybackBox { } shortFileNames.clear(); longFilePaths.clear(); - for (int i = numFilesToShow - 1; i >= 0; i--) { - JSONObject playbackFile = recentFilesArray.getJSONObject(i); + for (int i = 0; i < numFilesToShow; i++) { + JSONObject playbackFile = recentFilesArray.getJSONObject(recentFilesArray.size()-i-1); String shortFileName = playbackFile.getString("id"); String longFilePath = playbackFile.getString("filePath"); //truncate display name, if needed @@ -2837,18 +2138,15 @@ class RecentPlaybackBox { playbackHistoryFileExists = true; } catch (Exception e) { println("OpenBCI_GUI::Control Panel: Playback history file not found or other error."); + println(e.getMessage()); playbackHistoryFileExists = false; } recentPlaybackFilesHaveUpdated = true; } - void closeAllDropdowns(){ - cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").close(); - } - void createDropdown(String name, List _items){ - cp5_recentPlayback_dropdown.addScrollableList(name) + ScrollableList scrollList = new CustomScrollableList(cp5_recentPlayback_dropdown, name) .setOpen(false) .setColorBackground(color(31,69,110)) // text field bg color .setColorValueLabel(color(255)) // text color @@ -2861,7 +2159,7 @@ class RecentPlaybackBox { .setBarHeight(24) //height of top/primary bar .setItemHeight(24) //height of all item/dropdown bars .addItems(_items) // used to be .addItems(maxFreqList) - .setVisible(false) + .setVisible(true) ; cp5_recentPlayback_dropdown.getController(name) .getCaptionLabel() //the caption label is the text object in the primary bar @@ -2884,23 +2182,126 @@ class RecentPlaybackBox { } }; +class NovaXRBox { + private int x, y, w, h, padding; //size and position + private String boxLabel = "NOVAXR CONFIG"; + private String sampleRateLabel = "SAMPLE RATE"; + private ControlP5 localCP5; + private ScrollableList srList; + private ScrollableList modeList; + + NovaXRBox(int _x, int _y, int _w, int _h, int _padding) { + x = _x; + y = _y; + w = _w; + h = 104; + padding = _padding; + localCP5 = new ControlP5(ourApplet); + localCP5.setGraphics(ourApplet, 0,0); + localCP5.setAutoDraw(false); //Setting this saves code as cp5 elements will only be drawn/visible when [cp5].draw() is called + + modeList = createDropdown("novaXR_Modes", NovaXRMode.values()); + modeList.setPosition(x + padding, y + h - 24 - padding); + modeList.setSize(w - padding*2,(modeList.getItems().size()+1)*24); + srList = createDropdown("novaXR_SampleRates", NovaXRSR.values()); + srList.setPosition(x + w - padding*2 - 60*2, y + 16 + padding*2); + srList.setSize(120 + padding,(srList.getItems().size()+1)*24); + } + + public void update() { + // nothing + } + + public void draw() { + pushStyle(); + fill(boxColor); + stroke(boxStrokeColor); + strokeWeight(1); + //draw flexible grey background for this box + rect(x, y, w, h + modeList.getHeight() - padding*2); + popStyle(); + + pushStyle(); + fill(bgColor); + textFont(h3, 16); + textAlign(LEFT, TOP); + //draw text labels + text(boxLabel, x + padding, y + padding); + textAlign(LEFT, TOP); + textFont(p4, 14); + text(sampleRateLabel, x + padding, y + padding*2 + 18); + popStyle(); + + //draw cp5 last, on top of everything in this box + localCP5.draw(); + } + + public void mousePressed() { + } + + public void mouseReleased() { + } + + private ScrollableList createDropdown(String name, NovaXRSettingsEnum[] enumValues){ + ScrollableList list = new CustomScrollableList(localCP5, name) + .setOpen(false) + .setColorBackground(color(31,69,110)) // text field bg color + .setColorValueLabel(color(255)) // text color + .setColorCaptionLabel(color(255)) + .setColorForeground(color(125)) // border color when not selected + .setColorActive(color(150, 170, 200)) // border color when selected + .setBackgroundColor(150) + .setSize(w - padding*2, 24)//temporary size + .setBarHeight(24) //height of top/primary bar + .setItemHeight(24) //height of all item/dropdown bars + .setVisible(true) + ; + // for each entry in the enum, add it to the dropdown. + for (NovaXRSettingsEnum value : enumValues) { + // this will store the *actual* enum object inside the dropdown! + list.addItem(value.getName(), value); + } + //Style the text in the ScrollableList + list.getCaptionLabel() //the caption label is the text object in the primary bar + .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! + .setText(enumValues[0].getName()) + .setFont(h4) + .setSize(14) + .getStyle() //need to grab style before affecting the paddingTop + .setPaddingTop(4) + ; + list.getValueLabel() //the value label is connected to the text objects in the dropdown item bars + .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! + .setText(enumValues[0].getName()) + .setFont(h5) + .setSize(12) //set the font size of the item bars to 14pt + .getStyle() //need to grab style before affecting the paddingTop + .setPaddingTop(3) //4-pixel vertical offset to center text + ; + + return list; + } +}; + class PlaybackFileBox { - int x, y, w, h, padding; //size and position - int sampleDataButton_w = 100; - int sampleDataButton_h = 20; + private int x, y, w, h, padding; //size and position + private int sampleDataButton_w = 100; + private int sampleDataButton_h = 20; + private int titleH = 14; + private int buttonH = 24; PlaybackFileBox(int _x, int _y, int _w, int _h, int _padding) { x = _x; y = _y; w = _w; - h = 67; + h = buttonH + (_padding * 3) + titleH; padding = _padding; - selectPlaybackFile = new Button (x + padding, y + padding*2 + 13, w - padding*2, 24, "SELECT PLAYBACK FILE", fontInfo.buttonLabel_size); + selectPlaybackFile = new Button_obci (x + padding, y + padding*2 + titleH, w - padding*2, buttonH, "SELECT OPENBCI PLAYBACK FILE", fontInfo.buttonLabel_size); selectPlaybackFile.setHelpText("Click to open a dialog box to select an OpenBCI playback file (.txt or .csv)."); // Sample data button - sampleDataButton = new Button(x + w - sampleDataButton_w - padding, y + padding - 2, sampleDataButton_w, sampleDataButton_h, "Sample Data", 14); + sampleDataButton = new Button_obci(x + w - sampleDataButton_w - padding, y + padding - 2, sampleDataButton_w, sampleDataButton_h, "Sample Data", 14); sampleDataButton.setCornerRoundess((int)(sampleDataButton_h)); sampleDataButton.setFont(p4, 14); sampleDataButton.setColorNotPressed(color(57,128,204)); @@ -2931,20 +2332,10 @@ class PlaybackFileBox { class SDBox { final private String sdBoxDropdownName = "sdCardTimes"; - final private String[] sdTimesStrings = { - "Do not write to SD...", - "5 minute maximum", - "15 minute maximum", - "30 minute maximum", - "1 hour maximum", - "2 hours maximum", - "4 hour maximum", - "12 hour maximum", - "24 hour maximum" - }; - int x, y, w, h, padding; //size and position - ControlP5 cp5_sdBox; - boolean dropdownWasClicked = false; + private int x, y, w, h, padding; //size and position + private ControlP5 cp5_sdBox; + private ScrollableList sdList; + private int prevY; SDBox(int _x, int _y, int _w, int _h, int _padding) { x = _x; @@ -2952,36 +2343,21 @@ class SDBox { w = _w; h = 73; padding = _padding; + prevY = y; cp5_sdBox = new ControlP5(ourApplet); - createDropdown(sdBoxDropdownName, Arrays.asList(sdTimesStrings)); - cp5_sdBox.setGraphics(ourApplet, 0,0); - cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).setPosition(x + padding, y + padding*2 + 14); - cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).setSize(w - padding*2, int((sdTimesStrings.length / 2) + 1) * 24); cp5_sdBox.setAutoDraw(false); - //sdTimes = new MenuList(cp5, "sdTimes", w - padding*2, 108, p4); - //sdTimes.setPosition(x + padding, y + padding*2 + 13); - - serialPorts = Serial.list(); - - /* - //add items for the various SD times - sdTimes.addItem(makeItem("Do not write to SD...")); - sdTimes.addItem(makeItem("5 minute maximum")); - sdTimes.addItem(makeItem("15 minute maximum")); - sdTimes.addItem(makeItem("30 minute maximum")); - sdTimes.addItem(makeItem("1 hour maximum")); - sdTimes.addItem(makeItem("2 hours maximum")); - sdTimes.addItem(makeItem("4 hour maximum")); - sdTimes.addItem(makeItem("12 hour maximum")); - sdTimes.addItem(makeItem("24 hour maximum")); - - sdTimes.activeItem = sdSetting; //added to indicate default choice (sdSetting is in OpenBCI_GUI) - */ + createDropdown(sdBoxDropdownName); + cp5_sdBox.setGraphics(ourApplet, 0,0); + updatePosition(); + sdList.setSize(w - padding*2, (int((sdList.getItems().size()+1)/1.5)) * 24); } public void update() { - openCloseDropdown(); + if (y != prevY) { //When box's absolute y position changes, update cp5 + updatePosition(); + prevY = y; + } } public void draw() { @@ -2999,52 +2375,37 @@ class SDBox { pushStyle(); fill(150); - rect(cp5_sdBox.getController(sdBoxDropdownName).getPosition()[0]-1, cp5_sdBox.getController(sdBoxDropdownName).getPosition()[1]-1, cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).getWidth()+2, cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).getHeight()+2); - //cp5_sdBox.draw(); popStyle(); - - //set the correct position of the dropdown and make it visible if the SDBox class is being drawn - cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).setPosition(x + padding, y + padding*2 + 14); - cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).setVisible(true); cp5_sdBox.draw(); - - //sdTimes.setPosition(x + padding, y + padding*2 + 13); - //the drawing of the sdTimes is handled earlier in ControlPanel.draw() } - void createDropdown(String name, List _items){ + private void createDropdown(String name){ - cp5_sdBox.addScrollableList(name) + sdList = new CustomScrollableList(cp5_sdBox, name) .setOpen(false) .setColor(settings.dropdownColors) - /* - .setColorBackground(color(31,69,110)) // text field bg color - .setColorValueLabel(color(0)) // text color - .setColorCaptionLabel(color(255)) - .setColorForeground(color(125)) // border color when not selected - .setColorActive(color(150, 170, 200)) // border color when selected - */ - // .setColorCursor(color(26,26,26)) - - .setSize(w - padding*2,(_items.size()+1)*24)// + maxFreqList.size()) + .setBackgroundColor(150) + .setSize(w - padding*2, 2*24)//temporary size .setBarHeight(24) //height of top/primary bar .setItemHeight(24) //height of all item/dropdown bars - .addItems(_items) // used to be .addItems(maxFreqList) - .setVisible(false) + .setVisible(true) ; - cp5_sdBox.getController(name) - .getCaptionLabel() //the caption label is the text object in the primary bar + // for each entry in the enum, add it to the dropdown. + for (CytonSDMode mode : CytonSDMode.values()) { + // this will store the *actual* enum object inside the dropdown! + sdList.addItem(mode.getName(), mode); + } + sdList.getCaptionLabel() //the caption label is the text object in the primary bar .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! - .setText(sdTimesStrings[0]) + .setText(CytonSDMode.NO_WRITE.getName()) .setFont(p4) .setSize(14) .getStyle() //need to grab style before affecting the paddingTop .setPaddingTop(4) ; - cp5_sdBox.getController(name) - .getValueLabel() //the value label is connected to the text objects in the dropdown item bars + sdList.getValueLabel() //the value label is connected to the text objects in the dropdown item bars .toUpperCase(false) //DO NOT AUTOSET TO UPPERCASE!!! - .setText(sdTimesStrings[0]) + .setText(CytonSDMode.NO_WRITE.getName()) .setFont(h5) .setSize(12) //set the font size of the item bars to 14pt .getStyle() //need to grab style before affecting the paddingTop @@ -3052,77 +2413,40 @@ class SDBox { ; } - private void openCloseDropdown() { - //Close the dropdown if it is open and mouse is no longer over it - if (cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).isOpen()){ - if (!cp5_sdBox.getController(sdBoxDropdownName).isMouseOver()){ - //println("----Closing dropdown " + maxDurDropdownName); - cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).close(); - //lockElements(false); - } - - } - // Open the dropdown if it's not open, but not if it was recently clicked - // Makes sure dropdown stays closed after user selects an option - if (!dropdownWasClicked) { - if (!cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).isOpen()){ - if (cp5_sdBox.getController(sdBoxDropdownName).isMouseOver()){ - //println("++++Opening dropdown " + maxDurDropdownName); - cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).open(); - //lockElements(true); - } - } - } else { - // This flag is used to gate opening/closing the dropdown - dropdownWasClicked = false; - } - } - - void closeDropdown() { - cp5_sdBox.get(ScrollableList.class, sdBoxDropdownName).close(); - dropdownWasClicked = true; - //lockElements(false); - //println("---- DROPDOWN CLICKED -> CLOSING DROPDOWN"); + public void updatePosition() { + sdList.setPosition(x + padding, y + padding*2 + 14); } }; -////////////////////////////////////////////////////////////// -// Global function used by the above SDBox dropdown -void sdCardTimes (int n) { - //settings.cytonOBCIMaxFileSize = n; - sdSetting = n; - if (sdSetting != 0) { - output("OpenBCI microSD Setting = " + controlPanel.sdBox.sdTimesStrings[n] + " recording time"); - } else { - output("OpenBCI microSD Setting = " + controlPanel.sdBox.sdTimesStrings[n]); - } - verbosePrint("SD setting = " + controlPanel.sdBox.sdTimesStrings[n]); - - controlPanel.sdBox.closeDropdown(); - //println("ControlPanel: Cyton SD Card Duration: " + controlPanel.sdBox.sdTimesStrings[n]); -} class RadioConfigBox { - int x, y, w, h, padding; //size and position - String initial_message = "Having trouble connecting to your Cyton? Try AutoScan!\n\nUse this tool to get Cyton status or change settings."; - String last_message = initial_message; + private int x, y, w, h, padding; //size and position + private String initial_message = "Having trouble connecting to your Cyton? Try AutoScan!\n\nUse this tool to get Cyton status or change settings."; + private String last_message = initial_message; public boolean isShowing; + private RadioConfig cytonRadioCfg; + private int headerH = 15; + private int autoscanH = 45; + private int buttonH = 24; + private int statusWindowH = 115; RadioConfigBox(int _x, int _y, int _w, int _h, int _padding) { x = _x + _w; y = _y; w = _w + 10; - h = 275; //255 + 20 for larger autoscan button + h = (_padding*6) + headerH + (buttonH*2) + autoscanH + statusWindowH; padding = _padding; isShowing = false; - - //typical button height + 20 for larger autoscan button - autoscan = new Button(x + padding, y + padding + 18, w-(padding*2), 24 + 20, "AUTOSCAN", fontInfo.buttonLabel_size); - //smaller buttons below autoscan - getChannel = new Button(x + padding, y + padding*3 + 18 + 24 + 44, (w-padding*3)/2, 24, "GET CHANNEL", fontInfo.buttonLabel_size); - systemStatus = new Button(x + padding, y + padding*2 + 18 + 44, (w-padding*3)/2, 24, "STATUS", fontInfo.buttonLabel_size); - setChannel = new Button(x + 2*padding + (w-padding*3)/2, y + padding*2 + 18 + 44, (w-padding*3)/2, 24, "CHANGE CHAN.", fontInfo.buttonLabel_size); - ovrChannel = new Button(x + 2*padding + (w-padding*3)/2, y + padding*3 + 18 + 24 + 44, (w-padding*3)/2, 24, "OVERRIDE DONGLE", fontInfo.buttonLabel_size); + cytonRadioCfg = new RadioConfig(); + + //typical button height + 20 for larger autoscan button, full box width minus padding + autoscan = new Button_obci(x + padding, y + padding*2 + headerH, w-(padding*2), autoscanH, "AUTOSCAN", fontInfo.buttonLabel_size); + //smaller buttons below autoscan - left column + systemStatus = new Button_obci(x + padding, y + padding*3 + headerH + autoscanH, (w-padding*4)/2, buttonH, "STATUS", fontInfo.buttonLabel_size); + getChannel = new Button_obci(x + padding, y + padding*4 + headerH + buttonH + autoscanH, (w-padding*4)/2, buttonH, "GET CHANNEL", fontInfo.buttonLabel_size); + //right column + setChannel = new Button_obci(x + 2*padding + (w-padding*3)/2, y + padding*3 + headerH + autoscanH, (w-padding*3)/2, 24, "CHANGE CHAN.", fontInfo.buttonLabel_size); + ovrChannel = new Button_obci(x + 2*padding + (w-padding*3)/2, y + padding*4 + headerH + buttonH + autoscanH, (w-padding*3)/2, buttonH, "OVERRIDE DONGLE", fontInfo.buttonLabel_size); //Set help text @@ -3155,108 +2479,40 @@ class RadioConfigBox { } public void print_onscreen(String localstring){ + pushStyle(); textAlign(LEFT); fill(bgColor); - rect(x + padding, y + (padding*8) + 33 + (24*2), w-(padding*2), 135 - 21 - padding); //13 + 20 = 33 for larger autoscan + rect(x + padding, y + padding*5 + headerH + buttonH*2 + autoscanH, w-(padding*2), statusWindowH); fill(255); textFont(h3, 15); - text(localstring, x + padding + 5, y + (padding*8) + 5 + (24*2) + 35, (w-padding*3 ), 135 - 24 - padding -15); //15 + 20 = 35 + text(localstring, x + padding + 5, y + padding*6 + headerH + buttonH*2 + autoscanH, w - padding*3, statusWindowH - padding); + popStyle(); this.last_message = localstring; } -}; -class WifiConfigBox { - int x, y, w, h, padding; //size and position - String last_message = ""; - boolean isShowing; - - WifiConfigBox(int _x, int _y, int _w, int _h, int _padding) { - x = _x + _w; - y = _y; - w = _w; - h = 255; - padding = _padding; - isShowing = false; - - getTypeOfAttachedBoard = new Button(x + padding, y + padding*2 + 18, (w-padding*3)/2, 24, "OPENBCI BOARD", fontInfo.buttonLabel_size); - getIpAddress = new Button(x + 2*padding + (w-padding*3)/2, y + padding*2 + 18, (w-padding*3)/2, 24, "IP ADDRESS", fontInfo.buttonLabel_size); - getMacAddress = new Button(x + padding, y + padding*3 + 18 + 24, (w-padding*3)/2, 24, "MAC ADDRESS", fontInfo.buttonLabel_size); - getFirmwareVersion = new Button(x + 2*padding + (w-padding*3)/2, y + padding*3 + 18 + 24, (w-padding*3)/2, 24, "FIRMWARE VERS.", fontInfo.buttonLabel_size); - eraseCredentials = new Button(x + padding, y + padding*4 + 18 + 24*2, w-(padding*2), 24, "ERASE NETWORK CREDENTIALS", fontInfo.buttonLabel_size); - - //Set help text - getTypeOfAttachedBoard.setHelpText("Get the type of OpenBCI board attached to the WiFi Shield"); - getIpAddress.setHelpText("Get the IP Address of the WiFi shield"); - getMacAddress.setHelpText("Get the MAC Address of the WiFi shield"); - getFirmwareVersion.setHelpText("Get the firmware version of the WiFi Shield"); - eraseCredentials.setHelpText("Erase the store credentials on the WiFi Shield to join another wireless network. Always remove WiFi Shield from OpenBCI board prior to erase and WiFi Shield will become a hotspot again."); + public void getChannel() { + cytonRadioCfg.get_channel(RadioConfigBox.this); } - public void update() {} - public void draw() { - pushStyle(); - fill(boxColor); - stroke(boxStrokeColor); - strokeWeight(1); - rect(x, y, w, h); - fill(bgColor); - textFont(h3, 16); - textAlign(LEFT, TOP); - text("WIFI CONFIGURATION", x + padding, y + padding); - popStyle(); - getTypeOfAttachedBoard.draw(); - getIpAddress.draw(); - getMacAddress.draw(); - getFirmwareVersion.draw(); - eraseCredentials.draw(); - - this.print_onscreen(last_message); + public void setChannel(int val) { + cytonRadioCfg.set_channel(RadioConfigBox.this, val); } - public void updateMessage(String str) { - last_message = str; + public void setChannelOverride(int val) { + cytonRadioCfg.set_channel_over(RadioConfigBox.this, val); } - public void print_onscreen(String localstring){ - textAlign(LEFT); - fill(bgColor); - rect(x + padding, y + (padding*8) + 13 + (24*2), w-(padding*2), 135 - 21 - padding); - fill(255); - textFont(h3, 15); - text(localstring, x + padding + 10, y + (padding*8) + 5 + (24*2) + 15, (w-padding*3 ), 135 - 24 - padding -15); + public void scanChannels() { + cytonRadioCfg.scan_channels(RadioConfigBox.this); } -}; -class SDConverterBox { - int x, y, w, h, padding; //size and position - - SDConverterBox(int _x, int _y, int _w, int _h, int _padding) { - x = _x; - y = _y; - w = _w; - h = 67; - padding = _padding; - - selectSDFile = new Button (x + padding, y + padding*2 + 13, w - padding*2, 24, "SELECT SD FILE", fontInfo.buttonLabel_size); - selectSDFile.setHelpText("Click here to select an SD file generated by Cyton or Cyton+Daisy and convert to plain text format."); + public void getSystemStatus() { + cytonRadioCfg.system_status(RadioConfigBox.this); } - public void update() { - } - - public void draw() { - pushStyle(); - fill(boxColor); - stroke(boxStrokeColor); - strokeWeight(1); - rect(x, y, w, h); - fill(bgColor); - textFont(h3, 16); - textAlign(LEFT, TOP); - text("CONVERT SD FOR PLAYBACK", x + padding, y + padding); - popStyle(); - - selectSDFile.draw(); + public void closeSerialPort() { + print_onscreen(""); + cytonRadioCfg.closeSerialPort(); } }; @@ -3273,7 +2529,7 @@ class ChannelPopup { padding = _padding; clicked = false; - channelList = new MenuList(cp5Popup, "channelListCP", w - padding*2, 140, p4); + channelList = new MenuList(cp5Popup, "channelListCP", w - padding*2, 140, p3); channelList.setPosition(x+padding, y+padding*3); for (int i = 1; i < 26; i++) { @@ -3314,7 +2570,7 @@ class PollPopup { padding = _padding; clicked = false; - pollList = new MenuList(cp5Popup, "pollList", w - padding*2, 140, p4); + pollList = new MenuList(cp5Popup, "pollList", w - padding*2, 140, p3); pollList.setPosition(x+padding, y+padding*3); for (int i = 0; i < 256; i++) { @@ -3352,7 +2608,7 @@ class InitBox { h = 50; padding = _padding; - initSystemButton = new Button (padding, y + padding, w-padding*2, h - padding*2, "START SESSION", fontInfo.buttonLabel_size); + initSystemButton = new Button_obci (padding, y + padding, w-padding*2, h - padding*2, "START SESSION", fontInfo.buttonLabel_size); } public void update() { @@ -3385,6 +2641,14 @@ Map makeItem(String theHeadline) { return m; } +//makeItem function used by MenuList class below +Map makeItem(String theHeadline, int value) { + Map m = new HashMap(); + m.put("headline", theHeadline); + m.put("value", value); + return m; +} + //makeItem function used by MenuList class below Map makeItem(String theHeadline, String theSubline, String theCopy) { Map m = new HashMap(); @@ -3414,7 +2678,7 @@ public class MenuList extends controlP5.Controller { boolean updateMenu; int hoverItem = -1; int activeItem = -1; - PFont menuFont = p4; + PFont menuFont; int padding = 7; MenuList(ControlP5 c, String theName, int theWidth, int theHeight, PFont theFont) { @@ -3425,9 +2689,7 @@ public class MenuList extends controlP5.Controller { final ControlP5 cc = c; //allows check for isLocked() below final String _theName = theName; - menuFont = p4; - getValueLabel().setSize(14); - getCaptionLabel().setSize(14); + menuFont = theFont; setView(new ControllerView() { diff --git a/OpenBCI_GUI/DataLogger.pde b/OpenBCI_GUI/DataLogger.pde new file mode 100644 index 000000000..7e7458953 --- /dev/null +++ b/OpenBCI_GUI/DataLogger.pde @@ -0,0 +1,170 @@ +class DataLogger { + //variables for writing EEG data out to a file + private DataWriterODF fileWriterODF; + private DataWriterBDF fileWriterBDF; + + DataLogger() { + + } + + public void initialize() { + + } + + public void uninitialize() { + closeLogFile(); //close log file + } + + public void update() { + limitRecordingFileDuration(); + + saveNewData(); + } + + + private void saveNewData() { + //If data is available, save to playback file... + if(!settings.isLogFileOpen()) { + return; + } + + double[][] newData = currentBoard.getFrameData(); + + switch (outputDataSource) { + case OUTPUT_SOURCE_ODF: + fileWriterODF.append(newData); + break; + case OUTPUT_SOURCE_BDF: + fileWriterBDF.writeRawData_dataPacket(newData); + break; + case OUTPUT_SOURCE_NONE: + default: + // Do nothing... + break; + } + } + + public void limitRecordingFileDuration() { + if (settings.isLogFileOpen() && outputDataSource == OUTPUT_SOURCE_ODF && settings.maxLogTimeReached()) { + println("DataLogging: Max recording duration reached for OpenBCI data format. Creating a new recording file in the session folder."); + closeLogFile(); + openNewLogFile(directoryManager.getFileNameDateTime()); + settings.setLogFileStartTime(System.nanoTime()); + } + } + + public void onStartStreaming() { + if (outputDataSource > OUTPUT_SOURCE_NONE && eegDataSource != DATASOURCE_PLAYBACKFILE) { + //open data file if it has not already been opened + if (!settings.isLogFileOpen()) { + openNewLogFile(directoryManager.getFileNameDateTime()); + } + settings.setLogFileStartTime(System.nanoTime()); + } + } + + public void onStopStreaming() { + //Close the log file when using OpenBCI Data Format (.txt) + if (outputDataSource == OUTPUT_SOURCE_ODF) closeLogFile(); + } + + public float getSecondsWritten() { + if (outputDataSource == OUTPUT_SOURCE_ODF && fileWriterODF != null) { + return float(fileWriterODF.getRowsWritten())/currentBoard.getSampleRate(); + } + + if (outputDataSource == OUTPUT_SOURCE_BDF && fileWriterBDF != null) { + return fileWriterBDF.getRecordsWritten(); + } + + return 0.f; + } + + private void openNewLogFile(String _fileName) { + //close the file if it's open + switch (outputDataSource) { + case OUTPUT_SOURCE_ODF: + openNewLogFileODF(_fileName); + break; + case OUTPUT_SOURCE_BDF: + openNewLogFileBDF(_fileName); + break; + case OUTPUT_SOURCE_NONE: + default: + // Do nothing... + break; + } + settings.setLogFileIsOpen(true); + } + + /** + * @description Opens (and closes if already open) and BDF file. BDF is the + * biosemi data format. + * @param `_fileName` {String} - The meat of the file name + */ + private void openNewLogFileBDF(String _fileName) { + if (fileWriterBDF != null) { + println("OpenBCI_GUI: closing log file"); + closeLogFile(); + } + //open the new file + fileWriterBDF = new DataWriterBDF(_fileName); + + output_fname = fileWriterBDF.fname; + println("OpenBCI_GUI: openNewLogFile: opened BDF output file: " + output_fname); //Print filename of new BDF file to console + } + + /** + * @description Opens (and closes if already open) and ODF file. ODF is the + * openbci data format. + * @param `_fileName` {String} - The meat of the file name + */ + private void openNewLogFileODF(String _fileName) { + if (fileWriterODF != null) { + println("OpenBCI_GUI: closing log file"); + closeLogFile(); + } + //open the new file + fileWriterODF = new DataWriterODF(sessionName, _fileName); + + output_fname = fileWriterODF.fname; + println("OpenBCI_GUI: openNewLogFile: opened ODF output file: " + output_fname); //Print filename of new ODF file to console + } + + private void closeLogFile() { + switch (outputDataSource) { + case OUTPUT_SOURCE_ODF: + closeLogFileODF(); + break; + case OUTPUT_SOURCE_BDF: + closeLogFileBDF(); + break; + case OUTPUT_SOURCE_NONE: + default: + // Do nothing... + break; + } + settings.setLogFileIsOpen(false); + } + + /** + * @description Close an open BDF file. This will also update the number of data + * records. + */ + private void closeLogFileBDF() { + if (fileWriterBDF != null) { + fileWriterBDF.closeFile(); + } + fileWriterBDF = null; + } + + /** + * @description Close an open ODF file. + */ + private void closeLogFileODF() { + if (fileWriterODF != null) { + fileWriterODF.closeFile(); + } + fileWriterODF = null; + } +}; \ No newline at end of file diff --git a/OpenBCI_GUI/DataProcessing.pde b/OpenBCI_GUI/DataProcessing.pde index 873bc89f7..641e54569 100644 --- a/OpenBCI_GUI/DataProcessing.pde +++ b/OpenBCI_GUI/DataProcessing.pde @@ -4,9 +4,11 @@ //------------------------------------------------------------------------ import ddf.minim.analysis.*; //for FFT +import brainflow.DataFilter; +import brainflow.FilterTypes; + DataProcessing dataProcessing; String curTimestamp; -boolean hasRepeated = false; HashMap index_of_times; // indexes @@ -22,137 +24,37 @@ float playback_speed_fac = 1.0f; //make 1.0 for real-time. larger for faster p // Global Functions //------------------------------------------------------------------------ -//called from systemUpdate when mode=10 and isRunning = true -void process_input_file() throws Exception { - index_of_times = new HashMap(); - indices = 0; - try { - while (!hasRepeated) { - currentTableRowIndex = getPlaybackDataFromTable(playbackData_table, currentTableRowIndex, cyton.get_scale_fac_uVolts_per_count(), cyton.get_scale_fac_accel_G_per_count(), dataPacketBuff[lastReadDataPacketInd]); - if (curTimestamp != null) { - index_of_times.put(indices, curTimestamp.substring(1)); //remove white space from timestamp - } else { - index_of_times.put(indices, "notFound"); - } - indices++; - } - println("number of indexes "+indices); - println("Finished filling hashmap"); - has_processed = true; - } - catch (Exception e) { - e.printStackTrace(); - throw new Exception(); - } -} - -/*************************/ -int getDataIfAvailable(int pointCounter) { - - if (eegDataSource == DATASOURCE_CYTON) { - //get data from serial port as it streams in - //next, gather any new data into the "little buffer" - while ( (curDataPacketInd != lastReadDataPacketInd) && (pointCounter < nPointsPerUpdate)) { - lastReadDataPacketInd = (lastReadDataPacketInd+1) % dataPacketBuff.length; //increment to read the next packet - for (int Ichan=0; Ichan < nchan; Ichan++) { //loop over each cahnnel - //scale the data into engineering units ("microvolts") and save to the "little buffer" - yLittleBuff_uV[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan] * cyton.get_scale_fac_uVolts_per_count(); - } - for (int auxChan=0; auxChan < 3; auxChan++) auxBuff[auxChan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].auxValues[auxChan]; - pointCounter++; //increment counter for "little buffer" - } - } else if (eegDataSource == DATASOURCE_GANGLION) { - //get data from ble as it streams in - //next, gather any new data into the "little buffer" - while ( (curDataPacketInd != lastReadDataPacketInd) && (pointCounter < nPointsPerUpdate)) { - lastReadDataPacketInd = (lastReadDataPacketInd + 1) % dataPacketBuff.length; //increment to read the next packet - for (int Ichan=0; Ichan < nchan; Ichan++) { //loop over each cahnnel - //scale the data into engineering units ("microvolts") and save to the "little buffer" - yLittleBuff_uV[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan] * ganglion.get_scale_fac_uVolts_per_count(); - } - pointCounter++; //increment counter for "little buffer" - } - - } else { - // make or load data to simulate real time - - //has enough time passed? - int current_millis = millis(); - if (current_millis >= nextPlayback_millis) { - //prepare for next time - int increment_millis = int(round(float(nPointsPerUpdate)*1000.f/getSampleRateSafe())/playback_speed_fac); - if (nextPlayback_millis < 0) nextPlayback_millis = current_millis; - nextPlayback_millis += increment_millis; - - // generate or read the data - lastReadDataPacketInd = 0; - for (int i = 0; i < nPointsPerUpdate; i++) { - dataPacketBuff[lastReadDataPacketInd].sampleIndex++; - switch (eegDataSource) { - case DATASOURCE_SYNTHETIC: //use synthetic data (for GUI debugging) - synthesizeData(nchan, getSampleRateSafe(), cyton.get_scale_fac_uVolts_per_count(), dataPacketBuff[lastReadDataPacketInd]); - break; - case DATASOURCE_PLAYBACKFILE: - currentTableRowIndex=getPlaybackDataFromTable(playbackData_table, currentTableRowIndex, cyton.get_scale_fac_uVolts_per_count(), cyton.get_scale_fac_accel_G_per_count(), dataPacketBuff[lastReadDataPacketInd]); - break; - default: - //no action - } - //gather the data into the "little buffer" - for (int Ichan=0; Ichan < nchan; Ichan++) { - //scale the data into engineering units..."microvolts" - yLittleBuff_uV[Ichan][pointCounter] = dataPacketBuff[lastReadDataPacketInd].values[Ichan]* cyton.get_scale_fac_uVolts_per_count(); - } - - pointCounter++; - } //close the loop over data points - } // close "has enough time passed" - } - return pointCounter; -} - -RunningMean avgBitRate = new RunningMean(10); //10 point running average...at 5 points per second, this should be 2 second running average void processNewData() { - //compute instantaneous byte rate - float inst_byteRate_perSec = (int)(1000.f * ((float)(openBCI_byteCount - prevBytes)) / ((float)(millis() - prevMillis))); - - prevMillis=millis(); //store for next time - prevBytes = openBCI_byteCount; //store for next time - - //compute smoothed byte rate - avgBitRate.addValue(inst_byteRate_perSec); - byteRate_perSec = (int)avgBitRate.calcMean(); + List currentData = currentBoard.getData(getCurrentBoardBufferSize()); + int[] exgChannels = currentBoard.getEXGChannels(); + int channelCount = currentBoard.getNumEXGChannels(); //update the data buffers - for (int Ichan=0; Ichan < nchan; Ichan++) { - //append the new data to the larger data buffer...because we want the plotting routines - //to show more than just the most recent chunk of data. This will be our "raw" data. - appendAndShift(dataBuffY_uV[Ichan], yLittleBuff_uV[Ichan]); + for (int Ichan=0; Ichan < channelCount; Ichan++) { + + for(int i = 0; i < getCurrentBoardBufferSize(); i++) { + dataProcessingRawBuffer[Ichan][i] = (float)currentData.get(i)[exgChannels[Ichan]]; + } - //make a copy of the data that we'll apply processing to. This will be what is displayed on the full montage - dataBuffY_filtY_uV[Ichan] = dataBuffY_uV[Ichan].clone(); + dataProcessingFilteredBuffer[Ichan] = dataProcessingRawBuffer[Ichan].clone(); } - //if you want to, re-reference the montage to make it be a mean-head reference - if (false) rereferenceTheMontage(dataBuffY_filtY_uV); - //apply additional processing for the time-domain montage plot (ie, filtering) - dataProcessing.process(yLittleBuff_uV, dataBuffY_uV, dataBuffY_filtY_uV, fftBuff); + dataProcessing.process(dataProcessingFilteredBuffer, fftBuff); - dataProcessing_user.process(yLittleBuff_uV, dataBuffY_uV, dataBuffY_filtY_uV, fftBuff); dataProcessing.newDataToSend = true; //look to see if the latest data is railed so that we can notify the user on the GUI - for (int Ichan=0; Ichan < nchan; Ichan++) is_railed[Ichan].update(dataPacketBuff[lastReadDataPacketInd].values[Ichan]); + for (int Ichan=0; Ichan < nchan; Ichan++) is_railed[Ichan].update(dataProcessingRawBuffer[Ichan][dataProcessingRawBuffer.length-1], Ichan); //compute the electrode impedance. Do it in a very simple way [rms to amplitude, then uVolt to Volt, then Volt/Amp to Ohm] for (int Ichan=0; Ichan < nchan; Ichan++) { // Calculate the impedance - float impedance = (sqrt(2.0)*dataProcessing.data_std_uV[Ichan]*1.0e-6) / cyton.get_leadOffDrive_amps(); + float impedance = (sqrt(2.0)*dataProcessing.data_std_uV[Ichan]*1.0e-6) / BoardCytonConstants.leadOffDrive_amps; // Subtract the 2.2kOhm resistor - impedance -= cyton.get_series_resistor(); + impedance -= BoardCytonConstants.series_resistor_ohms; // Verify the impedance is not less than 0 if (impedance < 0) { // Incase impedance some how dipped below 2.2kOhm @@ -163,90 +65,7 @@ void processNewData() { } } -//helper function in handling the EEG data -void appendAndShift(float[] data, float[] newData) { - int nshift = newData.length; - int end = data.length-nshift; - for (int i=0; i < end; i++) { - data[i]=data[i+nshift]; //shift data points down by 1 - } - for (int i=0; i 2.0f*PI) sine_phase_rad[Ichan] -= 2.0f*PI; - val_uV += 10.0f * sqrt(2.0)*sin(sine_phase_rad[Ichan]); - } else if (Ichan==2) { - //15 Hz interference at 20 uVrms - sine_phase_rad[Ichan] += 2.0f*PI * 15.0f / fs_Hz; //15 Hz - if (sine_phase_rad[Ichan] > 2.0f*PI) sine_phase_rad[Ichan] -= 2.0f*PI; - val_uV += 20.0f * sqrt(2.0)*sin(sine_phase_rad[Ichan]); //20 uVrms - } else if (Ichan==3) { - //20 Hz interference at 30 uVrms - sine_phase_rad[Ichan] += 2.0f*PI * 20.0f / fs_Hz; //20 Hz - if (sine_phase_rad[Ichan] > 2.0f*PI) sine_phase_rad[Ichan] -= 2.0f*PI; - val_uV += 30.0f * sqrt(2.0)*sin(sine_phase_rad[Ichan]); //30 uVrms - } else if (Ichan==4) { - //25 Hz interference at 40 uVrms - sine_phase_rad[Ichan] += 2.0f*PI * 25.0f / fs_Hz; //25 Hz - if (sine_phase_rad[Ichan] > 2.0f*PI) sine_phase_rad[Ichan] -= 2.0f*PI; - val_uV += 40.0f * sqrt(2.0)*sin(sine_phase_rad[Ichan]); //40 uVrms - } else if (Ichan==5) { - //30 Hz interference at 50 uVrms - sine_phase_rad[Ichan] += 2.0f*PI * 30.0f / fs_Hz; //30 Hz - if (sine_phase_rad[Ichan] > 2.0f*PI) sine_phase_rad[Ichan] -= 2.0f*PI; - val_uV += 50.0f * sqrt(2.0)*sin(sine_phase_rad[Ichan]); //50 uVrms - } else if (Ichan==6) { - //60 Hz interference at 20 uVrms - sine_phase_rad[Ichan] += 2.0f*PI * 60.0f / fs_Hz; //60 Hz - if (sine_phase_rad[Ichan] > 2.0f*PI) sine_phase_rad[Ichan] -= 2.0f*PI; - val_uV += 20.0f * sqrt(2.0)*sin(sine_phase_rad[Ichan]); //20 uVrms - } - } else { - val_uV = 0.0f; - } - curDataPacket.values[Ichan] = (int) (0.5f+ val_uV / scale_fac_uVolts_per_count); //convert to counts, the 0.5 is to ensure rounding - } -} - -//some data initialization routines -void prepareData(float[] dataBuffX, float[][] dataBuffY_uV, float fs_Hz) { - //initialize the x and y data - int xoffset = dataBuffX.length - 1; - for (int i=0; i < dataBuffX.length; i++) { - dataBuffX[i] = ((float)(i-xoffset)) / fs_Hz; //x data goes from minus time up to zero - for (int Ichan = 0; Ichan < nchan; Ichan++) { - dataBuffY_uV[Ichan][i] = 0f; //make the y data all zeros - } - } -} - - -void initializeFFTObjects(FFT[] fftBuff, float[][] dataBuffY_uV, int Nfft, float fs_Hz) { +void initializeFFTObjects(FFT[] fftBuff, float[][] dataProcessingRawBuffer, int Nfft, float fs_Hz) { float[] fooData; for (int Ichan=0; Ichan < nchan; Ichan++) { @@ -255,90 +74,15 @@ void initializeFFTObjects(FFT[] fftBuff, float[][] dataBuffY_uV, int Nfft, float //do the FFT on the initial data if (isFFTFiltered == true) { - fooData = dataBuffY_filtY_uV[Ichan]; //use the filtered data for the FFT + fooData = dataProcessingFilteredBuffer[Ichan]; //use the filtered data for the FFT } else { - fooData = dataBuffY_uV[Ichan]; //use the raw data for the FFT + fooData = dataProcessingRawBuffer[Ichan]; //use the raw data for the FFT } fooData = Arrays.copyOfRange(fooData, fooData.length-Nfft, fooData.length); fftBuff[Ichan].forward(fooData); //compute FFT on this channel of data } } - -int getPlaybackDataFromTable(Table datatable, int currentTableRowIndex, float scale_fac_uVolts_per_count, float scale_fac_accel_G_per_count, DataPacket_ADS1299 curDataPacket) { - float val_uV = 0.0f; - float[] acc_G = new float[n_aux_ifEnabled]; - boolean acc_newData = false; - - //check to see if we can load a value from the table - if (currentTableRowIndex >= datatable.getRowCount()) { - //end of file - println("OpenBCI_GUI: getPlaybackDataFromTable: End of playback data file. Starting over..."); - hasRepeated = true; - currentTableRowIndex = 0; - } else { - //get the row - TableRow row = datatable.getRow(currentTableRowIndex); - currentTableRowIndex++; //increment to the next row - - //get each value - for (int Ichan=0; Ichan < nchan; Ichan++) { - if (isChannelActive(Ichan) && (Ichan < datatable.getColumnCount())) { - val_uV = row.getFloat(Ichan); - } else { - //use zeros for the missing channels - val_uV = 0.0f; - } - - //put into data structure - curDataPacket.values[Ichan] = (int) (0.5f+ val_uV / scale_fac_uVolts_per_count); //convert to counts, the 0.5 is to ensure rounding - } - - // get accelerometer data - try{ - for (int Iacc=0; Iacc < n_aux_ifEnabled; Iacc++) { - - if (Iacc < datatable.getColumnCount()) { - acc_G[Iacc] = row.getFloat(Iacc + nchan); - if (Float.isNaN(acc_G[Iacc])) { - acc_G[Iacc] = 0.0f; - } - } else { - //use zeros for bad data :) - acc_G[Iacc] = 0.0f; - } - - //put into data structure - curDataPacket.auxValues[Iacc] = (int) (0.5f+ acc_G[Iacc] / scale_fac_accel_G_per_count); //convert to counts, the 0.5 is to ensure rounding - - // Wangshu Dec.6 2016 - // as long as xyz are not zero at the same time, it should be fine...otherwise it will ignore it. - if (acc_G[Iacc] > 0.000001) { - acc_newData = true; - } - } - } catch (ArrayIndexOutOfBoundsException e){ - // println("Data does not exist... possibly an old file."); - } - if (acc_newData) { - for (int Iacc=0; Iacc < n_aux_ifEnabled; Iacc++) { - appendAndShift(accelerometerBuff[Iacc], acc_G[Iacc]); - } - } - // if available, get time stamp for use in playback - if (row.getColumnCount() >= nchan + NUM_ACCEL_DIMS + 2) { - try{ - if (!isOldData) curTimestamp = row.getString(row.getColumnCount() - 1); - } catch (ArrayIndexOutOfBoundsException e) { - println("Data does not exist... possibly an old file."); - } - } else { - curTimestamp = "-1"; - } - } //end else - return currentTableRowIndex; -} - //------------------------------------------------------------------------ // CLASSES //------------------------------------------------------------------------ @@ -346,15 +90,11 @@ int getPlaybackDataFromTable(Table datatable, int currentTableRowIndex, float sc class DataProcessing { private float fs_Hz; //sample rate private int nchan; - final int N_FILT_CONFIGS = 5; - FilterConstants[] filtCoeff_bp = new FilterConstants[N_FILT_CONFIGS]; - final int N_NOTCH_CONFIGS = 3; - FilterConstants[] filtCoeff_notch = new FilterConstants[N_NOTCH_CONFIGS]; - private int currentFilt_ind = 3; - private int currentNotch_ind = 0; // set to 0 to default to 60Hz, set to 1 to default to 50Hz float data_std_uV[]; float polarity[]; boolean newDataToSend; + BandPassRanges bpRange = BandPassRanges.FiveToFifty; + BandStopRanges bsRange = BandStopRanges.Sixty; final int[] processing_band_low_Hz = { 1, 4, 8, 13, 30 }; //lower bound for each frequency band of interest (2D classifier only) @@ -372,393 +112,140 @@ class DataProcessing { newDataToSend = false; avgPowerInBins = new float[nchan][processing_band_low_Hz.length]; headWidePower = new float[processing_band_low_Hz.length]; - - defineFilters(); //define the filters anyway just so that the code doesn't bomb - } - - //define filters depending on the sampling rate - private void defineFilters() { - int n_filt; - double[] b, a, b2, a2; - String filt_txt, filt_txt2; - String short_txt, short_txt2; - - //------------ loop over all of the pre-defined filter types ----------- - //------------ notch filters ------------ - n_filt = filtCoeff_notch.length; - for (int Ifilt=0; Ifilt < n_filt; Ifilt++) { - switch (Ifilt) { - case 0: - //60 Hz notch filter, 2nd Order Butterworth: [b, a] = butter(2,[59.0 61.0]/(fs_Hz / 2.0), 'stop') %matlab command - switch(int(fs_Hz)) { - case 125: - b2 = new double[] { 0.931378858122982, 3.70081291785747, 5.53903191270520, 3.70081291785747, 0.931378858122982 }; - a2 = new double[] { 1, 3.83246204081167, 5.53431749515949, 3.56916379490328, 0.867472133791669 }; - break; - case 200: - b2 = new double[] { 0.956543225556877, 1.18293615779028, 2.27881429174348, 1.18293615779028, 0.956543225556877 }; - a2 = new double[] { 1, 1.20922304075909, 2.27692490805580, 1.15664927482146, 0.914975834801436 }; - break; - case 250: - b2 = new double[] { 0.965080986344733, -0.242468320175764, 1.94539149412878, -0.242468320175764, 0.965080986344733 }; - a2 = new double[] { 1, -0.246778261129785, 1.94417178469135, -0.238158379221743, 0.931381682126902 }; - break; - case 500: - b2 = new double[] { 0.982385438526095, -2.86473884662109, 4.05324051877773, -2.86473884662109, 0.982385438526095}; - a2 = new double[] { 1, -2.89019558531207, 4.05293022193077, -2.83928210793009, 0.965081173899134 }; - break; - case 1000: - b2 = new double[] { 0.991153595101611, -3.68627799048791, 5.40978944177152, -3.68627799048791, 0.991153595101611 }; - a2 = new double[] { 1, -3.70265590760266, 5.40971118136100, -3.66990007337352, 0.982385450614122 }; - break; - case 1600: - b2 = new double[] { 0.994461788958027, -3.86796874670208, 5.75004904085114, -3.86796874670208, 0.994461788958027 }; - a2 = new double[] { 1, -3.87870938463296, 5.75001836883538, -3.85722810877252, 0.988954249933128 }; - break; - default: - println("EEG_Processing: *** ERROR *** Filters can only work at 125Hz, 200Hz, 250 Hz, 1000Hz or 1600Hz"); - b2 = new double[] { 1.0 }; - a2 = new double[] { 1.0 }; - } - filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "Notch 60Hz", "60Hz"); - break; - case 1: - //50 Hz notch filter, 2nd Order Butterworth: [b, a] = butter(2,[49.0 51.0]/(fs_Hz / 2.0), 'stop') - switch(int(fs_Hz)) { - case 125: - b2 = new double[] { 0.931378858122983, 3.01781693143160, 4.30731047590091, 3.01781693143160, 0.931378858122983 }; - a2 = new double[] { 1, 3.12516981877757, 4.30259605835520, 2.91046404408562, 0.867472133791670 }; - break; - case 200: - b2 = new double[] { 0.956543225556877, -2.34285519884863e-16, 1.91308645111375, -2.34285519884863e-16, 0.956543225556877 }; - a2 = new double[] { 1, -1.41553435639707e-15, 1.91119706742607, -1.36696209906972e-15, 0.914975834801435 }; - break; - case 250: - b2 = new double[] { 0.965080986344734, -1.19328255433335, 2.29902305135123, -1.19328255433335, 0.965080986344734 }; - a2 = new double[] { 1, -1.21449347931898, 2.29780334191380, -1.17207162934771, 0.931381682126901 }; - break; - case 500: - b2 = new double[] { 0.982385438526090, -3.17931708468811, 4.53709552901242, -3.17931708468811, 0.982385438526090 }; - a2 = new double[] { 1, -3.20756923909868, 4.53678523216547, -3.15106493027754, 0.965081173899133 }; - break; - case 1000: - b2 = new double[] { 0.991153595101607, -3.77064677042206, 5.56847615976560, -3.77064677042206, 0.991153595101607 }; - a2 = new double[] { 1, -3.78739953308251, 5.56839789935513, -3.75389400776205, 0.982385450614127 }; - break; - case 1600: - b2 = new double[] { 0.994461788958316, -3.90144402068168, 5.81543195046478, -3.90144402068168, 0.994461788958316 }; - a2 = new double[] { 1, -3.91227761329151, 5.81540127844733, -3.89061042807090, 0.988954249933127 }; - break; - default: - println("EEG_Processing: *** ERROR *** Filters can only work at 125Hz, 200Hz, 250 Hz, 1000Hz or 1600Hz"); - b2 = new double[] { 1.0 }; - a2 = new double[] { 1.0 }; - } - filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "Notch 50Hz", "50Hz"); - break; - case 2: - //no notch filter - b2 = new double[] { 1.0 }; - a2 = new double[] { 1.0 }; - filtCoeff_notch[Ifilt] = new FilterConstants(b2, a2, "No Notch", "None"); - break; - } - }// end loop over notch filters - - //------------ bandpass filters ------------ - n_filt = filtCoeff_bp.length; - for (int Ifilt=0; Ifilt= N_FILT_CONFIGS) currentFilt_ind = 0; - settings.dataProcessingBandpassSave = currentFilt_ind;//store the value to save bandpass setting + public synchronized void incrementFilterConfiguration() { + bpRange = bpRange.next(); } - public void incrementNotchConfiguration() { - //increment the index - currentNotch_ind++; - if (currentNotch_ind >= N_NOTCH_CONFIGS) currentNotch_ind = 0; - settings.dataProcessingNotchSave = currentNotch_ind; + public synchronized void incrementNotchConfiguration() { + bsRange = bsRange.next(); } - - public void process(float[][] data_newest_uV, //holds raw EEG data that is new since the last call - float[][] data_long_uV, //holds a longer piece of buffered EEG data, of same length as will be plotted on the screen - float[][] data_forDisplay_uV, //put data here that should be plotted on the screen - FFT[] fftData) { //holds the FFT (frequency spectrum) of the latest data + + private synchronized void processChannel(int Ichan, float[][] data_forDisplay_uV, float[] prevFFTdata) { int Nfft = getNfftSafe(); - //loop over each EEG channel - for (int Ichan=0; Ichan < nchan; Ichan++) { - - //filter the data in the time domain - filterIIR(filtCoeff_notch[currentNotch_ind].b, filtCoeff_notch[currentNotch_ind].a, data_forDisplay_uV[Ichan]); //notch - filterIIR(filtCoeff_bp[currentFilt_ind].b, filtCoeff_bp[currentFilt_ind].a, data_forDisplay_uV[Ichan]); //bandpass + double foo; - //compute the standard deviation of the filtered signal...this is for the head plot - float[] fooData_filt = dataBuffY_filtY_uV[Ichan]; //use the filtered data - fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data - data_std_uV[Ichan]=std(fooData_filt); //compute the standard deviation for the whole array "fooData_filt" - } //close loop over channels + //filter the data in the time domain + // todo use double arrays here and convert to float only to plot data + try { + double[] tempArray = floatToDoubleArray(data_forDisplay_uV[Ichan]); + if (bsRange != BandStopRanges.None) { + DataFilter.perform_bandstop(tempArray, currentBoard.getSampleRate(), (double)bsRange.getFreq(), (double)4.0, 2, FilterTypes.BUTTERWORTH.get_code(), (double)0.0); + } + if (bpRange != BandPassRanges.None) { + double centerFreq = (bpRange.getStart() + bpRange.getStop()) / 2.0; + double bandWidth = bpRange.getStop() - bpRange.getStart(); + DataFilter.perform_bandpass(tempArray, currentBoard.getSampleRate(), centerFreq, bandWidth, 2, FilterTypes.BUTTERWORTH.get_code(), (double)0.0); + } + doubleToFloatArray(tempArray, data_forDisplay_uV[Ichan]); + } catch (BrainFlowError e) { + e.printStackTrace(); + } + //compute the standard deviation of the filtered signal...this is for the head plot + float[] fooData_filt = dataProcessingFilteredBuffer[Ichan]; //use the filtered data + fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data + data_std_uV[Ichan]=std(fooData_filt); //compute the standard deviation for the whole array "fooData_filt" - // calculate FFT after filter + //copy the previous FFT data...enables us to apply some smoothing to the FFT data + for (int I=0; I < fftBuff[Ichan].specSize(); I++) { + prevFFTdata[I] = fftBuff[Ichan].getBand(I); //copy the old spectrum values + } - //println("PPP" + fftBuff[0].specSize()); - float prevFFTdata[] = new float[fftBuff[0].specSize()]; - double foo; + //prepare the data for the new FFT + float[] fooData; + if (isFFTFiltered == true) { + fooData = dataProcessingFilteredBuffer[Ichan]; //use the filtered data for the FFT + } else { + fooData = dataProcessingRawBuffer[Ichan]; //use the raw data for the FFT + } + fooData = Arrays.copyOfRange(fooData, fooData.length-Nfft, fooData.length); //trim to grab just the most recent block of data + float meanData = mean(fooData); //compute the mean + for (int I=0; I < fooData.length; I++) fooData[I] -= meanData; //remove the mean (for a better looking FFT - //update the FFT (frequency spectrum) - // println("nchan = " + nchan); - for (int Ichan=0; Ichan < nchan; Ichan++) { + //compute the FFT + fftBuff[Ichan].forward(fooData); //compute FFT on this channel of data - //copy the previous FFT data...enables us to apply some smoothing to the FFT data - for (int I=0; I < fftBuff[Ichan].specSize(); I++) { - prevFFTdata[I] = fftBuff[Ichan].getBand(I); //copy the old spectrum values - } + // FFT ref: https://www.mathworks.com/help/matlab/ref/fft.html + // first calculate double-sided FFT amplitude spectrum + for (int I=0; I <= Nfft/2; I++) { + fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) / Nfft)); + } + // then convert into single-sided FFT spectrum: DC & Nyquist (i=0 & i=N/2) remain the same, others multiply by two. + for (int I=1; I < Nfft/2; I++) { + fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) * 2)); + } - //prepare the data for the new FFT - float[] fooData; - if (isFFTFiltered == true) { - fooData = dataBuffY_filtY_uV[Ichan]; //use the filtered data for the FFT + //average the FFT with previous FFT data so that it makes it smoother in time + double min_val = 0.01d; + for (int I=0; I < fftBuff[Ichan].specSize(); I++) { //loop over each fft bin + if (prevFFTdata[I] < min_val) prevFFTdata[I] = (float)min_val; //make sure we're not too small for the log calls + foo = fftBuff[Ichan].getBand(I); + if (foo < min_val) foo = min_val; //make sure this value isn't too small + + if (true) { + //smooth in dB power space + foo = (1.0d-smoothFac[smoothFac_ind]) * java.lang.Math.log(java.lang.Math.pow(foo, 2)); + foo += smoothFac[smoothFac_ind] * java.lang.Math.log(java.lang.Math.pow((double)prevFFTdata[I], 2)); + foo = java.lang.Math.sqrt(java.lang.Math.exp(foo)); //average in dB space } else { - fooData = dataBuffY_uV[Ichan]; //use the raw data for the FFT + //smooth (average) in linear power space + foo = (1.0d-smoothFac[smoothFac_ind]) * java.lang.Math.pow(foo, 2); + foo+= smoothFac[smoothFac_ind] * java.lang.Math.pow((double)prevFFTdata[I], 2); + // take sqrt to be back into uV_rtHz + foo = java.lang.Math.sqrt(foo); } - fooData = Arrays.copyOfRange(fooData, fooData.length-Nfft, fooData.length); //trim to grab just the most recent block of data - float meanData = mean(fooData); //compute the mean - for (int I=0; I < fooData.length; I++) fooData[I] -= meanData; //remove the mean (for a better looking FFT - - //compute the FFT - fftBuff[Ichan].forward(fooData); //compute FFT on this channel of data - - //convert to uV_per_bin...still need to confirm the accuracy of this code. - //Do we need to account for the power lost in the windowing function? CHIP 2014-10-24 + fftBuff[Ichan].setBand(I, (float)foo); //put the smoothed data back into the fftBuff data holder for use by everyone else + // fftBuff[Ichan].setBand(I, 1.0f); // test + } //end loop over FFT bins - // FFT ref: https://www.mathworks.com/help/matlab/ref/fft.html - // first calculate double-sided FFT amplitude spectrum - for (int I=0; I <= Nfft/2; I++) { - fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) / Nfft)); - } - // then convert into single-sided FFT spectrum: DC & Nyquist (i=0 & i=N/2) remain the same, others multiply by two. - for (int I=1; I < Nfft/2; I++) { - fftBuff[Ichan].setBand(I, (float)(fftBuff[Ichan].getBand(I) * 2)); - } + // calculate single-sided psd by single-sided FFT amplitude spectrum + // PSD ref: https://www.mathworks.com/help/dsp/ug/estimate-the-power-spectral-density-in-matlab.html + // when i = 1 ~ (N/2-1), psd = (N / fs) * mag(i)^2 / 4 + // when i = 0 or i = N/2, psd = (N / fs) * mag(i)^2 - //average the FFT with previous FFT data so that it makes it smoother in time - double min_val = 0.01d; - for (int I=0; I < fftBuff[Ichan].specSize(); I++) { //loop over each fft bin - if (prevFFTdata[I] < min_val) prevFFTdata[I] = (float)min_val; //make sure we're not too small for the log calls - foo = fftBuff[Ichan].getBand(I); - if (foo < min_val) foo = min_val; //make sure this value isn't too small - - if (true) { - //smooth in dB power space - foo = (1.0d-smoothFac[smoothFac_ind]) * java.lang.Math.log(java.lang.Math.pow(foo, 2)); - foo += smoothFac[smoothFac_ind] * java.lang.Math.log(java.lang.Math.pow((double)prevFFTdata[I], 2)); - foo = java.lang.Math.sqrt(java.lang.Math.exp(foo)); //average in dB space - } else { - //smooth (average) in linear power space - foo = (1.0d-smoothFac[smoothFac_ind]) * java.lang.Math.pow(foo, 2); - foo+= smoothFac[smoothFac_ind] * java.lang.Math.pow((double)prevFFTdata[I], 2); - // take sqrt to be back into uV_rtHz - foo = java.lang.Math.sqrt(foo); - } - fftBuff[Ichan].setBand(I, (float)foo); //put the smoothed data back into the fftBuff data holder for use by everyone else - // fftBuff[Ichan].setBand(I, 1.0f); // test - } //end loop over FFT bins - - // calculate single-sided psd by single-sided FFT amplitude spectrum - // PSD ref: https://www.mathworks.com/help/dsp/ug/estimate-the-power-spectral-density-in-matlab.html - // when i = 1 ~ (N/2-1), psd = (N / fs) * mag(i)^2 / 4 - // when i = 0 or i = N/2, psd = (N / fs) * mag(i)^2 - - for (int i = 0; i < processing_band_low_Hz.length; i++) { - float sum = 0; - // int binNum = 0; - for (int Ibin = 0; Ibin <= Nfft/2; Ibin ++) { // loop over FFT bins - float FFT_freq_Hz = fftBuff[Ichan].indexToFreq(Ibin); // center frequency of this bin - float psdx = 0; - // if the frequency matches a band - if (FFT_freq_Hz >= processing_band_low_Hz[i] && FFT_freq_Hz < processing_band_high_Hz[i]) { - if (Ibin != 0 && Ibin != Nfft/2) { - psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/getSampleRateSafe() / 4; - } - else { - psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/getSampleRateSafe(); - } - sum += psdx; - // binNum ++; + for (int i = 0; i < processing_band_low_Hz.length; i++) { + float sum = 0; + // int binNum = 0; + for (int Ibin = 0; Ibin <= Nfft/2; Ibin ++) { // loop over FFT bins + float FFT_freq_Hz = fftBuff[Ichan].indexToFreq(Ibin); // center frequency of this bin + float psdx = 0; + // if the frequency matches a band + if (FFT_freq_Hz >= processing_band_low_Hz[i] && FFT_freq_Hz < processing_band_high_Hz[i]) { + if (Ibin != 0 && Ibin != Nfft/2) { + psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/currentBoard.getSampleRate() / 4; + } + else { + psdx = fftBuff[Ichan].getBand(Ibin) * fftBuff[Ichan].getBand(Ibin) * Nfft/currentBoard.getSampleRate(); } + sum += psdx; + // binNum ++; } - avgPowerInBins[Ichan][i] = sum; // total power in a band - // println(i, binNum, sum); } + avgPowerInBins[Ichan][i] = sum; // total power in a band + // println(i, binNum, sum); + } + } + + public void process(float[][] data_forDisplay_uV, FFT[] fftData) { //holds the FFT (frequency spectrum) of the latest data + + float prevFFTdata[] = new float[fftBuff[0].specSize()]; + + for (int Ichan=0; Ichan < nchan; Ichan++) { + processChannel(Ichan, data_forDisplay_uV, prevFFTdata); } //end the loop over channels. + for (int i = 0; i < processing_band_low_Hz.length; i++) { float sum = 0; @@ -774,13 +261,13 @@ class DataProcessing { //find strongest channel int refChanInd = findMax(data_std_uV); //println("EEG_Processing: strongest chan (one referenced) = " + (refChanInd+1)); - float[] refData_uV = dataBuffY_filtY_uV[refChanInd]; //use the filtered data + float[] refData_uV = dataProcessingFilteredBuffer[refChanInd]; //use the filtered data refData_uV = Arrays.copyOfRange(refData_uV, refData_uV.length-((int)fs_Hz), refData_uV.length); //just grab the most recent second of data //compute polarity of each channel for (int Ichan=0; Ichan < nchan; Ichan++) { - float[] fooData_filt = dataBuffY_filtY_uV[Ichan]; //use the filtered data + float[] fooData_filt = dataProcessingFilteredBuffer[Ichan]; //use the filtered data fooData_filt = Arrays.copyOfRange(fooData_filt, fooData_filt.length-((int)fs_Hz), fooData_filt.length); //just grab the most recent second of data float dotProd = calcDotProduct(fooData_filt, refData_uV); if (dotProd >= 0.0f) { @@ -790,4 +277,4 @@ class DataProcessing { } } } -} +} \ No newline at end of file diff --git a/OpenBCI_GUI/DataProcessing_User.pde b/OpenBCI_GUI/DataProcessing_User.pde deleted file mode 100644 index db6ffe148..000000000 --- a/OpenBCI_GUI/DataProcessing_User.pde +++ /dev/null @@ -1,34 +0,0 @@ - -//------------------------------------------------------------------------ -// Global Variables & Instances -//------------------------------------------------------------------------ - -DataProcessing_User dataProcessing_user; - -//------------------------------------------------------------------------ -// Classes -//------------------------------------------------------------------------ - -class DataProcessing_User { - private float fs_Hz; //sample rate - private int n_chan; - - //class constructor - DataProcessing_User(int NCHAN, float sample_rate_Hz) { - n_chan = NCHAN; - fs_Hz = sample_rate_Hz; - } - - //add some functions here...if you'd like - - //here is the processing routine called by the OpenBCI main program...update this with whatever you'd like to do - public void process(float[][] data_newest_uV, //holds raw bio data that is new since the last call - float[][] data_long_uV, //holds a longer piece of buffered EEG data, of same length as will be plotted on the screen - float[][] data_forDisplay_uV, //this data has been filtered and is ready for plotting on the screen - FFT[] fftData) { //holds the FFT (frequency spectrum) of the latest data - - //for example, you could loop over each EEG channel to do some sort of time-domain processing - //using the sample values that have already been filtered, as will be plotted on the display - float EEG_value_uV; - } -}; diff --git a/OpenBCI_GUI/DataSource.pde b/OpenBCI_GUI/DataSource.pde new file mode 100644 index 000000000..2401ac8e3 --- /dev/null +++ b/OpenBCI_GUI/DataSource.pde @@ -0,0 +1,33 @@ + +interface DataSource { + + public boolean initialize(); + + public void uninitialize(); + + public void update(); + + public int getNumEXGChannels(); + + public double[][] getFrameData(); + + public List getData(int maxSamples); + + public void startStreaming(); + + public void stopStreaming(); + + public int getSampleRate(); + + public void setEXGChannelActive(int channelIndex, boolean active); + + public boolean isEXGChannelActive(int channelIndex); + + public int[] getEXGChannels(); + + public int getTimestampChannel(); + + public int getSampleNumberChannel(); + + public int getTotalChannelCount(); +}; diff --git a/OpenBCI_GUI/DataSourcePlayback.pde b/OpenBCI_GUI/DataSourcePlayback.pde new file mode 100644 index 000000000..cf54cd7a6 --- /dev/null +++ b/OpenBCI_GUI/DataSourcePlayback.pde @@ -0,0 +1,362 @@ +class DataSourcePlayback implements DataSource, AccelerometerCapableBoard, AnalogCapableBoard, DigitalCapableBoard, EDACapableBoard, PPGCapableBoard, FileBoard { + private String playbackFilePath; + private ArrayList rawData; + private int currentSample; + private int timeOfLastUpdateMS; + private String underlyingClassName; + + private boolean initialized = false; + private boolean streaming = false; + + private Board underlyingBoard = null; + private int sampleRate = -1; + + DataSourcePlayback(String filePath) { + playbackFilePath = filePath; + } + + @Override + public boolean initialize() { + currentSample = 0; + String[] lines = loadStrings(playbackFilePath); + + if(!parseHeader(lines)) { + return false; + } + if(!instantiateUnderlyingBoard()) { + return false; + } + if(!parseData(lines)) { + return false; + } + + return true; + } + + @Override + public void uninitialize() { + initialized = false; + } + + protected boolean parseHeader(String[] lines) { + for (String line : lines) { + if (!line.startsWith("%")) { + break; // reached end of header + } + + //only needed for synthetic board. can delete if we get rid of synthetic board. + if (line.startsWith("%Number of channels")) { + int startIndex = line.indexOf('=') + 2; + String nchanStr = line.substring(startIndex); + int chanCount = Integer.parseInt(nchanStr); + updateToNChan(chanCount); // sythetic board depends on this being set before it's initialized + } + + // some boards have configurable sample rate, so read it from header + if (line.startsWith("%Sample Rate")) { + int startIndex = line.indexOf('=') + 2; + int endIndex = line.indexOf("Hz") - 1; + + String hzString = line.substring(startIndex, endIndex); + sampleRate = Integer.parseInt(hzString); + } + + // used to figure out the underlying board type + if (line.startsWith("%Board")) { + int startIndex = line.indexOf('=') + 2; + underlyingClassName = line.substring(startIndex); + } + } + + boolean success = sampleRate > 0 && underlyingClassName != ""; + if(!success) { + outputError("Playback file does not contain the required header data."); + } + return success; + } + + protected boolean instantiateUnderlyingBoard() { + try { + // get class from name + Class boardClass = Class.forName(underlyingClassName); + // find default contructor (since this is processing, PApplet is required arg in all constructors) + Constructor constructor = boardClass.getConstructor(OpenBCI_GUI.class); + underlyingBoard = (Board)constructor.newInstance(ourApplet); + } catch (Exception e) { + outputError("Cannot instantiate underlying board of class " + underlyingClassName); + println(e.getMessage()); + e.printStackTrace(); + return false; + } + + return underlyingBoard != null; + } + + protected boolean parseData(String[] lines) { + int dataStart; + // set data start to first line of data (skip header) + for (dataStart = 0; dataStart < lines.length; dataStart++) { + String line = lines[dataStart]; + if (!line.startsWith("%")) { + dataStart++; // skip column names + break; + } + } + + int dataLength = lines.length - dataStart; + rawData = new ArrayList(dataLength); + + for (int iData=0; iData getData(int maxSamples) { + int firstSample = max(0, currentSample - maxSamples); + List result = rawData.subList(firstSample, currentSample); + + // if needed, pad the beginning of the array with empty data + if (maxSamples > currentSample) { + int sampleDiff = maxSamples - currentSample; + + double[] emptyData = new double[getTotalChannelCount()]; + ArrayList newResult = new ArrayList(maxSamples); + for (int i=0; i= getTotalSamples(); + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/DataSourceSDCard.pde b/OpenBCI_GUI/DataSourceSDCard.pde new file mode 100644 index 000000000..4950843ab --- /dev/null +++ b/OpenBCI_GUI/DataSourceSDCard.pde @@ -0,0 +1,295 @@ +class DataSourceSDCard implements DataSource, FileBoard, AccelerometerCapableBoard { + + private String filePath; + private int samplingRate; + private ArrayList data; + private int[] exgChannels; + private int totalChannels; + private boolean streaming; + private double startTime; + private int counter; + private int currentSample; + private int timeOfLastUpdateMS; + private double accel_x; + private double accel_y; + private double accel_z; + + DataSourceSDCard(String filePath) { + this.filePath = filePath; + samplingRate = 0; + data = new ArrayList(); + streaming = false; + exgChannels = null; + counter = 0; + startTime = 0.0; + timeOfLastUpdateMS = 0; + totalChannels = 0; + accel_x = 0.0; + accel_y = 0.0; + accel_z = 0.0; + } + + @Override + public boolean initialize() { + try { + File file = new File(this.filePath); + Scanner reader = new Scanner(file); + startTime = millis() / 1000.0; + while (reader.hasNextLine()) { + String line = reader.nextLine(); + String[] splitted = line.split(","); + if (splitted.length < 8) { + continue; + } + if (splitted.length < 15) { + if (samplingRate == 0) { + samplingRate = 250; + exgChannels = new int[] {1,2,3,4,5,6,7,8}; + totalChannels = 13; + } + } + else { + if (samplingRate == 0) { + samplingRate = 125; + exgChannels = new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; + totalChannels = 21; + } + } + try { + parseRow(splitted, exgChannels.length); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + counter++; + } + reader.close(); + println("Initialized, data len is " + data.size() + " Num EXG Channels is " + exgChannels.length); + return true; + } catch (FileNotFoundException e) { + e.printStackTrace(); + return false; + } + } + + private void parseRow(String[] row, int numChannels) { + double[] line = new double[totalChannels]; + if (row.length < numChannels - 1) { + return; + } + for (int i = 0; i < numChannels + 1; i++) { + double res = 0.0; + if (i == 0) { + res = Integer.parseInt(row[i], 16); + } + else { + res = parseInt24Hex(row[i]) * BoardCytonConstants.scale_fac_uVolts_per_count; + } + line[i] = res; + } + // new accel + if (row.length >= numChannels + 4) { + accel_x = parseInt16Hex(row[numChannels + 1]); + accel_y = parseInt16Hex(row[numChannels + 2]); + accel_z = parseInt16Hex(row[numChannels + 3]); + } + line[line.length - 4] = BoardCytonConstants.accelScale * accel_x; + line[line.length - 3] = BoardCytonConstants.accelScale * accel_y; + line[line.length - 2] = BoardCytonConstants.accelScale * accel_z; + // add timestamp + double delay = 1.0 / samplingRate; + double timestamp = startTime + counter * delay; + line[line.length - 1] = timestamp; + data.add(line); + } + + @Override + public void uninitialize() { + samplingRate = 0; + data = new ArrayList(); + streaming = false; + exgChannels = null; + counter = 0; + startTime = 0.0; + timeOfLastUpdateMS = 0; + totalChannels = 0; + accel_x = 0.0; + accel_y = 0.0; + accel_z = 0.0; + } + + @Override + public void update() { + if (!streaming) { + return; // do not update + } + + float sampleRateMS = getSampleRate() / 1000.f; + int timeElapsedMS = millis() - timeOfLastUpdateMS; + int numNewSamplesThisFrame = floor(timeElapsedMS * sampleRateMS); + + // account for the fact that each update will not coincide with a sample exactly. + // to keep the streaming rate accurate, we increment the time of last update + // based on how many samples we incremented this frame. + timeOfLastUpdateMS += numNewSamplesThisFrame / sampleRateMS; + + currentSample += numNewSamplesThisFrame; + + if (endOfFileReached()) { + stopButtonWasPressed(); + } + + // don't go beyond raw data array size + currentSample = min(currentSample, data.size() - 1); + } + + @Override + public void startStreaming() { + streaming = true; + timeOfLastUpdateMS = millis(); + } + + @Override + public void stopStreaming() { + streaming = false; + } + + @Override + public int getSampleRate() { + return samplingRate; + } + + @Override + public void setEXGChannelActive(int channelIndex, boolean active) { + outputWarn("Deactivating channels is not possible for Playback board."); + } + + @Override + public boolean isEXGChannelActive(int channelIndex) { + return true; + } + + @Override + public int[] getEXGChannels() { + return exgChannels; + } + + @Override + public int getNumEXGChannels() { + return getEXGChannels().length; + } + + @Override + public int getTimestampChannel() { + return totalChannels - 1; + } + + @Override + public int getSampleNumberChannel() { + return 0; + } + + @Override + public int getTotalChannelCount() { + return totalChannels; + } + + @Override + public double[][] getFrameData() { + // empty data (for now?) + return new double[getTotalChannelCount()][0]; + } + + @Override + public List getData(int maxSamples) { + int firstSample = max(0, currentSample - maxSamples); + List result = data.subList(firstSample, currentSample); + + // if needed, pad the beginning of the array with empty data + if (maxSamples > currentSample) { + int sampleDiff = maxSamples - currentSample; + + double[] emptyData = new double[getTotalChannelCount()]; + ArrayList newResult = new ArrayList(maxSamples); + for (int i=0; i= getTotalSamples(); + } + + private int parseInt24Hex(String hex) { + if (hex.charAt(0) > '7') { // if the number is negative + hex = "FF" + hex; // keep it negative + } else { // if the number is positive + hex = "00" + hex; // keep it positive + } + + return unhex(hex); + } + + private int parseInt16Hex(String hex) { + if (hex.charAt(0) > '7') { // if the number is negative + hex = "FFFF" + hex; // keep it negative + } else { // if the number is positive + hex = "0000" + hex; // keep it positive + } + + return unhex(hex); + } + +} diff --git a/OpenBCI_GUI/DataLogging.pde b/OpenBCI_GUI/DataWriterBDF.pde similarity index 63% rename from OpenBCI_GUI/DataLogging.pde rename to OpenBCI_GUI/DataWriterBDF.pde index 2bec371bf..e262d8c95 100644 --- a/OpenBCI_GUI/DataLogging.pde +++ b/OpenBCI_GUI/DataWriterBDF.pde @@ -1,326 +1,6 @@ -//////////////////////////////////////////////////////////// -// Purpose: Handle OpenBCI Data Format and BDF+ file writing -// Created: Chip Audette May 2, 2014 -// Modified: Richard Waltman July 1, 2019 -// -//////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------ -// Global Functions -//------------------------------------------------------------------------ - -void openNewLogFile(String _fileName) { - //close the file if it's open - switch (outputDataSource) { - case OUTPUT_SOURCE_ODF: - openNewLogFileODF(_fileName); - break; - case OUTPUT_SOURCE_BDF: - openNewLogFileBDF(_fileName); - break; - case OUTPUT_SOURCE_NONE: - default: - // Do nothing... - break; - } - settings.setLogFileIsOpen(true); -} - -/** - * @description Opens (and closes if already open) and BDF file. BDF is the - * biosemi data format. - * @param `_fileName` {String} - The meat of the file name - */ -void openNewLogFileBDF(String _fileName) { - if (fileoutput_bdf != null) { - println("OpenBCI_GUI: closing log file"); - closeLogFile(); - } - //open the new file - fileoutput_bdf = new OutputFile_BDF(getSampleRateSafe(), nchan, _fileName); - - output_fname = fileoutput_bdf.fname; - println("OpenBCI_GUI: openNewLogFile: opened BDF output file: " + output_fname); //Print filename of new BDF file to console -} - -/** - * @description Opens (and closes if already open) and ODF file. ODF is the - * openbci data format. - * @param `_fileName` {String} - The meat of the file name - */ -void openNewLogFileODF(String _fileName) { - if (fileoutput_odf != null) { - println("OpenBCI_GUI: closing log file"); - closeLogFile(); - } - //open the new file - fileoutput_odf = new OutputFile_rawtxt(getSampleRateSafe(), sessionName, _fileName); - - output_fname = fileoutput_odf.fname; - println("OpenBCI_GUI: openNewLogFile: opened ODF output file: " + output_fname); //Print filename of new ODF file to console -} - -void closeLogFile() { - switch (outputDataSource) { - case OUTPUT_SOURCE_ODF: - closeLogFileODF(); - break; - case OUTPUT_SOURCE_BDF: - closeLogFileBDF(); - break; - case OUTPUT_SOURCE_NONE: - default: - // Do nothing... - break; - } - settings.setLogFileIsOpen(false); -} - -/** - * @description Close an open BDF file. This will also update the number of data - * records. - */ -void closeLogFileBDF() { - if (fileoutput_bdf != null) { - fileoutput_bdf.closeFile(); - } - fileoutput_bdf = null; -} - -/** - * @description Close an open ODF file. - */ -void closeLogFileODF() { - if (fileoutput_odf != null) { - fileoutput_odf.closeFile(); - } - fileoutput_odf = null; -} - -void fileSelected(File selection) { //called by the Open File dialog box after a file has been selected - if (selection == null) { - println("fileSelected: no selection so far..."); - } else { - //inputFile = selection; - playbackData_fname = selection.getAbsolutePath(); //<>// //<>// - } -} - -String getDateString() { - String fname = year() + "-"; - if (month() < 10) fname=fname+"0"; - fname = fname + month() + "-"; - if (day() < 10) fname = fname + "0"; - fname = fname + day(); - - fname = fname + "_"; - if (hour() < 10) fname = fname + "0"; - fname = fname + hour() + "-"; - if (minute() < 10) fname = fname + "0"; - fname = fname + minute() + "-"; - if (second() < 10) fname = fname + "0"; - fname = fname + second(); - return fname; -} - -//these functions are relevant to convertSDFile -void createPlaybackFileFromSD() { - logFileName = settings.guiDataPath+"SDconverted-"+getDateString()+".csv"; - dataWriter = createWriter(logFileName); - dataWriter.println("%OBCI SD Convert - " + getDateString()); - dataWriter.println("%"); - dataWriter.println("%Sample Rate = 250.0 Hz"); - dataWriter.println("%First Column = SampleIndex"); - dataWriter.println("%Last Column = Timestamp"); - dataWriter.println("%Other Columns = EEG data in microvolts followed by Accel Data (in G) interleaved with Aux Data"); - -} - -void sdFileSelected(File selection) { - if (selection == null) { - println("Window was closed or the user hit cancel."); - } else { - println("User selected " + selection.getAbsolutePath()); - dataReader = createReader(selection.getAbsolutePath()); // ("positions.txt"); - controlPanel.convertingSD = true; - println("Timing SD file conversion..."); - thatTime = millis(); - } -} - -//------------------------------------------------------------------------ -// CLASSES -//------------------------------------------------------------------------ - -//write data to a text file -public class OutputFile_rawtxt { - PrintWriter output; - String fname; - private int rowsWritten; - private long logFileStartTime; - - OutputFile_rawtxt(float fs_Hz) { - - //build up the file name - fname = settings.guiDataPath+"OpenBCI-RAW-"; - - //add year month day to the file name - fname = fname + year() + "-"; - if (month() < 10) fname=fname+"0"; - fname = fname + month() + "-"; - if (day() < 10) fname = fname + "0"; - fname = fname + day(); - - //add hour minute sec to the file name - fname = fname + "_"; - if (hour() < 10) fname = fname + "0"; - fname = fname + hour() + "-"; - if (minute() < 10) fname = fname + "0"; - fname = fname + minute() + "-"; - if (second() < 10) fname = fname + "0"; - fname = fname + second(); - - //add the extension - fname = fname + ".txt"; - - //open the file - output = createWriter(fname); - - //add the header - writeHeader(fs_Hz); - - //init the counter - rowsWritten = 0; - } - - //variation on constructor to have custom name - OutputFile_rawtxt(float fs_Hz, String _sessionName, String _fileName) { - settings.setSessionPath(settings.recordingsPath + "OpenBCISession_" + _sessionName + File.separator); - fname = settings.getSessionPath(); - fname += "OpenBCI-RAW-"; - fname += _fileName; - fname += ".txt"; - output = createWriter(fname); //open the file - writeHeader(fs_Hz); //add the header - rowsWritten = 0; //init the counter - } - - public void writeHeader(float fs_Hz) { - output.println("%OpenBCI Raw EEG Data"); - output.println("%Number of channels = " + nchan); - output.println("%Sample Rate = " + fs_Hz + " Hz"); - output.println("%First Column = SampleIndex"); - output.println("%Last Column = Timestamp "); - output.println("%Other Columns = EEG data in microvolts followed by Accel Data (in G) interleaved with Aux Data"); - output.flush(); - } - - public void writeRawData_dataPacket(DataPacket_ADS1299 data, float scale_to_uV, float scale_for_aux, int stopByte, long timestamp) { - //get current date time with Date() - if (output != null) { - output.print(Integer.toString(data.sampleIndex)); - writeValues(data.values,scale_to_uV); - if (eegDataSource == DATASOURCE_GANGLION) { - writeAccValues(data.auxValues, scale_for_aux); - } else { - if (stopByte == 0xC1) { - writeAuxValues(data); - } else { - writeAccValues(data.auxValues, scale_for_aux); - } - } - output.print( ", " + dateFormat.format(new Date(timestamp))); - output.print( ", " + timestamp); - output.println(); rowsWritten++; - //output.flush(); - } - } - - private void writeValues(int[] values, float scale_fac) { - int nVal = values.length; - for (int Ival = 0; Ival < nVal; Ival++) { - output.print(", "); - output.print(String.format(Locale.US, "%.2f", scale_fac * float(values[Ival]))); - } - } - - private void writeAccValues(int[] values, float scale_fac) { - int nVal = values.length; - for (int Ival = 0; Ival < nVal; Ival++) { - output.print(", "); - output.print(String.format(Locale.US, "%.3f", scale_fac * float(values[Ival]))); - } - } - - private void writeAuxValues(DataPacket_ADS1299 data) { - if (eegDataSource == DATASOURCE_CYTON) { - // println("board mode: " + cyton.getBoardMode()); - if (cyton.getBoardMode() == BoardMode.DIGITAL) { - if (cyton.isWifi()) { - output.print(", " + ((data.auxValues[0] & 0xFF00) >> 8)); - output.print(", " + (data.auxValues[0] & 0xFF)); - output.print(", " + data.auxValues[1]); - } else { - output.print(", " + ((data.auxValues[0] & 0xFF00) >> 8)); - output.print(", " + (data.auxValues[0] & 0xFF)); - output.print(", " + ((data.auxValues[1] & 0xFF00) >> 8)); - output.print(", " + (data.auxValues[1] & 0xFF)); - output.print(", " + data.auxValues[2]); - } - } else if (cyton.getBoardMode() == BoardMode.ANALOG) { - if (cyton.isWifi()) { - output.print(", " + data.auxValues[0]); - output.print(", " + data.auxValues[1]); - } else { - output.print(", " + data.auxValues[0]); - output.print(", " + data.auxValues[1]); - output.print(", " + data.auxValues[2]); - } - } else if (cyton.getBoardMode() == BoardMode.MARKER) { - output.print(", " + data.auxValues[0]); - if ( data.auxValues[0] > 0) { - hub.validLastMarker = data.auxValues[0]; - } - - } else { - for (int Ival = 0; Ival < 3; Ival++) { - output.print(", " + data.auxValues[Ival]); - } - } - } else { - for (int i = 0; i < 3; i++) { - output.print(", " + (data.auxValues[i] & 0xFF)); - output.print(", " + ((data.auxValues[i] & 0xFF00) >> 8)); - } - } - } - - public void closeFile() { - output.flush(); - output.close(); - } - - public int getRowsWritten() { - return rowsWritten; - } - - public void limitRecordingFileDuration() { - if (settings.maxLogTimeReached()) { - println("DataLogging: Max recording duration reached for OpenBCI data format. Creating a new recording file in the session folder."); - closeLogFile(); - //open data file if it has not already been opened - if (!settings.isLogFileOpen()) { - if (eegDataSource == DATASOURCE_CYTON) openNewLogFile(getDateString()); - if (eegDataSource == DATASOURCE_GANGLION) openNewLogFile(getDateString()); - } - settings.setLogFileStartTime(System.nanoTime()); - } - } -}; - //write data to a text file in BDF+ format http://www.biosemi.com/faq/file_format.htm -public class OutputFile_BDF { +public class DataWriterBDF { private PrintWriter writer; private OutputStream dstream; // private FileOutputStream fstream; @@ -395,10 +75,6 @@ public class OutputFile_BDF { private String bdf_physical_minimum_ADC_24bit_ganglion = "-15686"; private String bdf_physical_maximum_ADC_24bit_ganglion = "15686"; - private final float ADS1299_Vref = 4.5f; //reference voltage for ADC in ADS1299. set by its hardware - private float ADS1299_gain = 24.0; //assumed gain setting for ADS1299. set by its Arduino code - private float scale_fac_uVolts_per_count = ADS1299_Vref / ((float)(pow(2,23)-1)) / ADS1299_gain * 1000000.f; //ADS1299 datasheet Table 7, confirmed through experiment - public boolean continuous = true; public boolean write_accel = true; @@ -440,7 +116,7 @@ public class OutputFile_BDF { private String nbSamplesPerDataRecordEEG[] = new String[nbChan]; private String reservedEEG[] = new String[nbChan]; - private String tempWriterPrefix = settings.recordingsPath+"temp.txt"; + private String tempWriterPrefix = directoryManager.getRecordingsPath()+"temp.txt"; private int fs_Hz = 250; private int accel_Hz = 25; @@ -467,43 +143,31 @@ public class OutputFile_BDF { /** * @description Creates an EDF writer! Name of output file based on current * date and time. - * @param `_fs_Hz` {float} - The sample rate of the data source. Going to be - * `250` for OpenBCI 32bit board, `125` for OpenBCI 32bit board + daisy, or - * `256` for the Ganglion. - * @param `_nbChan` {int} - The number of channels of the data source. Going to be - * `8` for OpenBCI 32bit board, `16` for OpenBCI 32bit board + daisy, or - * `4` for the Ganglion. * @constructor */ - OutputFile_BDF(float _fs_Hz, int _nbChan) { + DataWriterBDF() { fname = getFileName(); - fs_Hz = (int)_fs_Hz; - nbChan = _nbChan; + fs_Hz = currentBoard.getSampleRate(); + nbChan = currentBoard.getNumEXGChannels(); init(); } /** * @description Creates an EDF writer! The output file will contain the `_filename`. - * @param `_fs_Hz` {float} - The sample rate of the data source. Going to be - * `250` for OpenBCI 32bit board, `125` for OpenBCI 32bit board + daisy, or - * `256` for the Ganglion. - * @param `_nbChan` {int} - The number of channels of the data source. Going to be - * `8` for OpenBCI 32bit board, `16` for OpenBCI 32bit board + daisy, or - * `4` for the Ganglion. * @param `_fileName` {String} - Main component of the output file name. * @constructor */ - OutputFile_BDF(float _fs_Hz, int _nbChan, String _fileName) { + DataWriterBDF(String _fileName) { fname = getFileName(_fileName); - fs_Hz = (int)_fs_Hz; - nbChan = _nbChan; + fs_Hz = currentBoard.getSampleRate(); + nbChan = currentBoard.getNumEXGChannels(); init(); } - + /** * @description Used to initalize the writer. */ @@ -533,30 +197,32 @@ public class OutputFile_BDF { * @description Writes a raw data packet to the buffer. Also will flush the * buffer if it is filled with one second worth of data. Will also capture * the start time, or the first time a packet is recieved. - * @param `data` {DataPacket_ADS1299} - A data packet + * @param `data` double[][] - A data packet */ - public void writeRawData_dataPacket(DataPacket_ADS1299 data) { + public void writeRawData_dataPacket(double[][] data) { + for (int i=0; i= fs_Hz) { - arrayCopy(chanValBuf,chanValBuf_buffer); - if (eegDataSource == DATASOURCE_CYTON) { - arrayCopy(auxValBuf,auxValBuf_buffer); + writeChannelDataValues(data, i); + if (currentBoard instanceof AccelerometerCapableBoard) { + writeAuxDataValues(data, i); } + samplesInDataRecord++; + // writeValues(data.auxValues,scale_for_aux); + if (samplesInDataRecord >= fs_Hz) { + arrayCopy(chanValBuf,chanValBuf_buffer); + if (currentBoard instanceof AccelerometerCapableBoard) { + arrayCopy(auxValBuf,auxValBuf_buffer); + } - samplesInDataRecord = 0; - writeDataOut(); + samplesInDataRecord = 0; + writeDataOut(); + } } } @@ -944,7 +610,7 @@ public class OutputFile_BDF { * @returns {String} - A fully qualified name of an output file with `str`. */ private String getFileName(String s) { - String output = settings.recordingsPath+"OpenBCI-BDF-"; + String output = directoryManager.getRecordingsPath()+"OpenBCI-BDF-"; output += s; output += ".bdf"; return output; @@ -1125,17 +791,29 @@ public class OutputFile_BDF { } /** - * @description Moves a packet worth of data into channel buffer, also converts - * from Big Endian to Little Indian as per the specs of BDF+. + * @description Moves a packet worth of data into channel buffer. Reverses byte order. * Ref [1]: http://www.biosemi.com/faq/file_format.htm - * @param `values` {byte[][]} - A byte array that is n_chan X sample size (3) */ - private void writeChannelDataValues(byte[][] values) { + private void writeChannelDataValues(double[][] allData, int sampleIndex) { + int[] exgchannels = currentBoard.getEXGChannels(); + for (int i = 0; i < nbChan; i++) { + // [daniellasry 5/3/2020] This function has been updated to work + // with brainflow and the new data flow. + // the following lines convert the new input type (double) + // to the input type this function originally expected + // (24 bit integer in a byte array of length 3) + int value = (int)allData[exgchannels[i]][sampleIndex]; + ByteBuffer bb = ByteBuffer.allocate(4); + bb.putInt(value); + byte[] bytes = bb.array(); + // skip the first byte which should be full of zeroes anyway (24 bit int) + byte[] values = {bytes[1], bytes[2], bytes[3]}; + // Make the values little endian - chanValBuf[i][samplesInDataRecord][0] = swapByte(values[i][2]); - chanValBuf[i][samplesInDataRecord][1] = swapByte(values[i][1]); - chanValBuf[i][samplesInDataRecord][2] = swapByte(values[i][0]); + chanValBuf[i][samplesInDataRecord][0] = values[2]; + chanValBuf[i][samplesInDataRecord][1] = values[1]; + chanValBuf[i][samplesInDataRecord][2] = values[0]; } } @@ -1143,27 +821,40 @@ public class OutputFile_BDF { * @description Moves a packet worth of data into aux buffer, also converts * from Big Endian to Little Indian as per the specs of BDF+. * Ref [1]: http://www.biosemi.com/faq/file_format.htm - * @param `values` {byte[][]} - A byte array that is n_aux X sample size (3) */ - private void writeAuxDataValues(byte[][] values) { + private void writeAuxDataValues(double[][] allData, int sampleIndex) { + int[] accelChannels = ((AccelerometerCapableBoard)currentBoard).getAccelerometerChannels(); + for (int i = 0; i < nbAux; i++) { if (write_accel) { + // [daniellasry 5/3/2020] This function has been updated to work + // with brainflow and the new data flow. + // the following lines convert the new input type (double) + // to the input type this function originally expected + // (16 bit integer in a byte array of length 2) + int accelInt = (int)allData[accelChannels[i]][sampleIndex]; + ByteBuffer bb = ByteBuffer.allocate(4); + bb.putInt(accelInt); + byte[] bytes = bb.array(); + // skip the first 2 bytes which should be full of zeroes anyway (16 bit int) + byte[] values = {bytes[2], bytes[3]}; + // grab the lower part of boolean zeroPack = true; // shift right - int t = (int)values[i][0] & 0x0F; - values[i][0] = (byte)((int)values[i][0] >> 4); - if (values[i][0] >= 8) { + int t = (int)values[0] & 0x0F; + values[0] = (byte)((int)values[0] >> 4); + if (values[0] >= 8) { zeroPack = false; } - values[i][1] = (byte)((int)values[i][1] >> 4); - values[i][1] = (byte)((int)values[i][1] | t); + values[1] = (byte)((int)values[1] >> 4); + values[1] = (byte)((int)values[1] | t); if (!zeroPack) { - values[i][0] = (byte)((int)values[i][0] | 0xF0); + values[0] = (byte)((int)values[0] | 0xF0); } // make msb -> lsb - auxValBuf[i][samplesInDataRecord][0] = swapByte(values[i][1]); - auxValBuf[i][samplesInDataRecord][1] = swapByte(values[i][0]); + auxValBuf[i][samplesInDataRecord][0] = values[1]; + auxValBuf[i][samplesInDataRecord][1] = values[0]; // pad byte if (zeroPack) { auxValBuf[i][samplesInDataRecord][2] = (byte)0x00; @@ -1320,277 +1011,4 @@ public class OutputFile_BDF { } } -}; - -/////////////////////////////////////////////////////////////// -// -// Class: Table_CSV -// Purpose: Extend the Table class to handle data files with comment lines -// Created: Chip Audette May 2, 2014 -// -// Usage: Only invoke this object when you want to read in a data -// file in CSV format. Read it in at the time of creation via -// -// String fname = "myfile.csv"; -// TableCSV myTable = new TableCSV(fname); -// -/////////////////////////////////////////////////////////////// - -class Table_CSV extends Table { - private int sampleRate; - public int getSampleRate() { return sampleRate; } - Table_CSV(String fname) throws IOException { - init(); - readCSV(PApplet.createReader(createInput(fname))); - } - - //this function is nearly completely copied from parseBasic from Table.java - void readCSV(BufferedReader reader) throws IOException { - boolean header=false; //added by Chip, May 2, 2014; - boolean tsv = false; //added by Chip, May 2, 2014; - - String line = null; - int row = 0; - if (rowCount == 0) { - setRowCount(10); - } - //int prev = 0; //-1; - try { - while ( (line = reader.readLine ()) != null) { - //added by Chip, May 2, 2014 to ignore lines that are comments - if (line.charAt(0) == '%') { - if (line.length() > 18) { - if (line.charAt(1) == 'S') { - sampleRate = Integer.parseInt(line.substring(15, 18)); - if (sampleRate == 100 || sampleRate == 160) { - sampleRate = Integer.parseInt(line.substring(15, 19)); - } - println("Sample rate set to " + sampleRate); - } - } - println("readCSV: " + line); - continue; - } - - if (row == getRowCount()) { - setRowCount(row << 1); - } - if (row == 0 && header) { - setColumnTitles(tsv ? PApplet.split(line, '\t') : split(line,',')); - header = false; - } - else { - setRow(row, tsv ? PApplet.split(line, '\t') : split(line,',')); - row++; - } - - // this is problematic unless we're going to calculate rowCount first - if (row % 10000 == 0) { - try { - // Sleep this thread so that the GC can catch up - Thread.sleep(10); - } - catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - } catch (Exception e) { - throw new RuntimeException("Error reading table on line " + row, e); - } - // shorten or lengthen based on what's left - if (row != getRowCount()) { - setRowCount(row); - } - } -} - -////////////////////////////////// -// -// This collection of functions/methods - convertSDFile, createPlaybackFileFromSD, & sdFileSelected - contains code -// used to convert HEX files (stored by OpenBCI on the local SD) into text files that can be used for PLAYBACK mode. -// Created: Conor Russomanno - 10/22/14 (based on code written by Joel Murphy summer 2014) -// Updated: Joel Murphy - 6/26/17 -// -////////////////////////////////// - -//variables for SD file conversion -BufferedReader dataReader; -String dataLine; -PrintWriter dataWriter; -String h; -float[] floatData = new float[20]; -float[] intData = new float[20]; -String logFileName; -String[] hexNums; -long thisTime; -long thatTime; -boolean printNextLine = false; - -public void convertSDFile() { - try { - dataLine = dataReader.readLine(); - } - catch (IOException e) { - e.printStackTrace(); - dataLine = null; - } - - if (dataLine == null) { - // Stop reading because of an error or file is empty - thisTime = millis() - thatTime; - controlPanel.convertingSD = false; - println("nothing left in file"); - println("SD file conversion took "+thisTime+" mS"); - outputSuccess("SD file converted to " + logFileName); - dataWriter.flush(); - dataWriter.close(); - } - else - { - hexNums = splitTokens(dataLine, ","); - - if (hexNums[0].charAt(0) == '%') { - // println(dataLine); - // dataWriter.println(dataLine); - println("convertSDFile: " + dataLine); - printNextLine = true; - } else { - if (hexNums.length < 13){ - convert8channelLine(); - } else { - convert16channelLine(); - } - if(printNextLine){ - printNextLine = false; - } - } - } -} - -void convert16channelLine() { - String consoleMsg = ""; - if(printNextLine){ - for(int i=0; i 1){ - dataWriter.print(", "); - consoleMsg += ", "; - } - } - dataWriter.println(); - println("convert16channelLine: " + consoleMsg); - return; - } - for (int i=0; i 0) { - if (h.charAt(0) > '7') { // if the number is negative - h = "FF" + hexNums[i]; // keep it negative - } else { // if the number is positive - h = "00" + hexNums[i]; // keep it positive - } - if (i > 16) { // accelerometer data needs another byte - if (h.charAt(0) == 'F') { - h = "FF" + h; - } else { - h = "00" + h; - } - } - } - // println(h); // use for debugging - if (h.length()%2 == 0 && h.length() <= 10) { // make sure this is a real number - floatData[i] = unhex(h); - } else { - floatData[i] = 0; - } - - if (i>=1 && i<=16) { - floatData[i] *= cyton.get_scale_fac_uVolts_per_count(); - }else if(i != 0){ - floatData[i] *= cyton.get_scale_fac_accel_G_per_count(); - } - - if(i == 0){ - dataWriter.print(int(floatData[i])); // print the sample counter - }else{ - dataWriter.print(floatData[i]); // print the current channel value - } - if (i < hexNums.length-1) { // print the current channel value - dataWriter.print(","); // print "," separator - } - } - dataWriter.println(); -} - -void convert8channelLine() { - String consoleMsg = ""; - if(printNextLine){ - for(int i=0; i 1){ - dataWriter.print(", "); - consoleMsg += ", "; - } - } - dataWriter.println(); - println("convert8channelLine: " + consoleMsg); - return; - } - for (int i=0; i 0) { - if (h.charAt(0) > '7') { // if the number is negative - h = "FF" + hexNums[i]; // keep it negative - } else { // if the number is positive - h = "00" + hexNums[i]; // keep it positive - } - if (i > 8) { // accelerometer data needs another byte - if (h.charAt(0) == 'F') { - h = "FF" + h; - } else { - h = "00" + h; - } - } - } - // println(h + " " + h.length()); // use for debugging - if (h.length() > 8) { - break; - } - if (h.length()%2 == 0) { // make sure this is a real number - floatData[i] = unhex(h); - } else { - floatData[i] = 0; - } - - if (i>=1 && i<=8) { - floatData[i] *= cyton.get_scale_fac_uVolts_per_count(); - }else if(i != 0){ - floatData[i] *= cyton.get_scale_fac_accel_G_per_count(); - } - - if(i == 0){ - dataWriter.print(int(floatData[i])); // print the sample counter - }else{ - dataWriter.print(floatData[i]); // print the current channel value - } - if (i < hexNums.length-1) { - dataWriter.print(","); // print "," separator - } - } - dataWriter.println(); -} +}; \ No newline at end of file diff --git a/OpenBCI_GUI/DataWriterODF.pde b/OpenBCI_GUI/DataWriterODF.pde new file mode 100644 index 000000000..d669de6f8 --- /dev/null +++ b/OpenBCI_GUI/DataWriterODF.pde @@ -0,0 +1,67 @@ + +//write data to a text file +public class DataWriterODF { + private PrintWriter output; + private String fname; + private int rowsWritten; + private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + private Board streamingBoard; + + //variation on constructor to have custom name + DataWriterODF(String _sessionName, String _fileName) { + streamingBoard = (Board)currentBoard; + settings.setSessionPath(directoryManager.getRecordingsPath() + "OpenBCISession_" + _sessionName + File.separator); + fname = settings.getSessionPath(); + fname += "OpenBCI-RAW-"; + fname += _fileName; + fname += ".txt"; + output = createWriter(fname); //open the file + writeHeader(); //add the header + rowsWritten = 0; //init the counter + } + + public void writeHeader() { + output.println("%OpenBCI Raw EEG Data"); + output.println("%Number of channels = " + nchan); + output.println("%Sample Rate = " + streamingBoard.getSampleRate() + " Hz"); + output.println("%Board = " + streamingBoard.getClass().getName()); + + String[] colNames = streamingBoard.getChannelNames(); + + for (int i=0; i -1; +} + +/** + * @description Helper function to determine if the system is windows or not. + * @return {boolean} true if os is windows, false otherwise. + */ +private boolean isWindows() { + return System.getProperty("os.name").toLowerCase().indexOf("windows") > -1; +} + +/** + * @description Helper function to determine if the system is macOS or not. + * @return {boolean} true if os is windows, false otherwise. + */ +private boolean isMac() { + return !isWindows() && !isLinux(); +} + //compute the standard deviation float std(float[] data) { @@ -124,38 +116,6 @@ float filterWEA_1stOrderIIR(float[] filty, float learn_fac, float filt_state) { return prev; } -void filterIIR(double[] filt_b, double[] filt_a, float[] data) { - int Nback = filt_b.length; - double[] prev_y = new double[Nback]; - double[] prev_x = new double[Nback]; - - //step through data points - for (int i = 0; i < data.length; i++) { - //shift the previous outputs - for (int j = Nback-1; j > 0; j--) { - prev_y[j] = prev_y[j-1]; - prev_x[j] = prev_x[j-1]; - } - - //add in the new point - prev_x[0] = data[i]; - - //compute the new data point - double out = 0; - for (int j = 0; j < Nback; j++) { - out += filt_b[j]*prev_x[j]; - if (j > 0) { - out -= filt_a[j]*prev_y[j]; - } - } - - //save output value - prev_y[0] = out; - data[i] = (float)out; - } -} - - void removeMean(float[] filty, int Nback) { float meanVal = mean(filty,Nback); for (int i=0; i < filty.length; i++) { @@ -163,20 +123,31 @@ void removeMean(float[] filty, int Nback) { } } -void rereferenceTheMontage(float[][] data) { - int n_chan = data.length; - int n_points = data[0].length; - float sum, mean; +double[] floatToDoubleArray(float[] array) { + double[] res = new double[array.length]; + for (int i = 0; i < res.length; i++) { + res[i] = (double)array[i]; + } + return res; +} + +float[] doubleToFloatArray(double[] array) { + float[] res = new float[array.length]; + for (int i = 0; i < res.length; i++) { + res[i] = (float)array[i]; + } + return res; +} - //loop over all data points - for (int Ipoint=0;Ipoint second) { + newNumber -= i; + } + else { + newNumber += i; + } - for (int i=0; i < nAuxValues; i++) { - interpolated.auxValues[i] = lerpInt(first.auxValues[i], second.auxValues[i], bias); + result[i] = newNumber; } - interpolated.sampleIndex = lerpInt(first.sampleIndex, second.sampleIndex, bias); - - return interpolated; + return result; } //------------------------------------------------------------------------ // Classes //------------------------------------------------------------------------ -class RunningMean { - private float[] values; - private int cur_ind = 0; - RunningMean(int N) { - values = new float[N]; - cur_ind = 0; - } - public void addValue(float val) { - values[cur_ind] = val; - cur_ind = (cur_ind + 1) % values.length; - } - public float calcMean() { - return mean(values); - } -}; - -class DataPacket_ADS1299 { - private final int rawAdsSize = 3; - private final int rawAuxSize = 2; - - int sampleIndex; - int[] values; - int[] auxValues; - byte[][] rawValues; - byte[][] rawAuxValues; - boolean interpolated; - - //constructor, give it "nValues", which should match the number of values in the - //data payload in each data packet from the Arduino. This is likely to be at least - //the number of EEG channels in the OpenBCI system (ie, 8 channels if a single OpenBCI - //board) plus whatever auxiliary data the Arduino is sending. - DataPacket_ADS1299(int nValues, int nAuxValues) { - values = new int[nValues]; - auxValues = new int[nAuxValues]; - rawValues = new byte[nValues][rawAdsSize]; - rawAuxValues = new byte[nAuxValues][rawAdsSize]; - interpolated = false; // default - } - - int copyTo(DataPacket_ADS1299 target) { return copyTo(target, 0, 0); } - int copyTo(DataPacket_ADS1299 target, int target_startInd_values, int target_startInd_aux) { - target.sampleIndex = sampleIndex; - return copyValuesAndAuxTo(target, target_startInd_values, target_startInd_aux); - } - int copyValuesAndAuxTo(DataPacket_ADS1299 target, int target_startInd_values, int target_startInd_aux) { - int nvalues = values.length; - for (int i=0; i < nvalues; i++) { - target.values[target_startInd_values + i] = values[i]; - target.rawValues[target_startInd_values + i] = rawValues[i]; - } - nvalues = auxValues.length; - for (int i=0; i < nvalues; i++) { - target.auxValues[target_startInd_aux + i] = auxValues[i]; - target.rawAuxValues[target_startInd_aux + i] = rawAuxValues[i]; - } - return 0; - } -}; - class DataStatus { public boolean is_railed; - private int threshold_railed; + private double threshold_railed; public boolean is_railed_warn; - private int threshold_railed_warn; + private double threshold_railed_warn; DataStatus(int thresh_railed, int thresh_railed_warn) { + // convert int24 value to uV is_railed = false; + // convert thresholds to uV threshold_railed = thresh_railed; is_railed_warn = false; threshold_railed_warn = thresh_railed_warn; } - public void update(int data_value) { + public void update(float data_value, int channel) { is_railed = false; - if (abs(data_value) >= threshold_railed) is_railed = true; is_railed_warn = false; - if (abs(data_value) >= threshold_railed_warn) is_railed_warn = true; + if (currentBoard instanceof ADS1299SettingsBoard) { + double scaler = (4.5 / (pow (2, 23) - 1) / ((ADS1299SettingsBoard)currentBoard).getGain(channel) * 1000000.); + if (abs(data_value) >= threshold_railed * scaler) { + is_railed = true; + } + if (abs(data_value) >= threshold_railed_warn * scaler) { + is_railed_warn = true; + } + } } }; @@ -336,24 +247,6 @@ class FilterConstants { } }; -class DetectionData_FreqDomain { - public float inband_uV = 0.0f; - public float inband_freq_Hz = 0.0f; - public float guard_uV = 0.0f; - public float thresh_uV = 0.0f; - public boolean isDetected = false; - - DetectionData_FreqDomain() { - } -}; - -class GraphDataPoint { - public double x; - public double y; - public String x_units; - public String y_units; -}; - class PlotFontInfo { String fontName = "fonts/Raleway-Regular.otf"; int axisLabel_size = 16; @@ -383,6 +276,7 @@ class TextBox { alignH = LEFT; alignV = BOTTOM; } + public void setFontSize(int size) { fontSize = size; font = p5; diff --git a/OpenBCI_GUI/FileBoard.pde b/OpenBCI_GUI/FileBoard.pde new file mode 100644 index 000000000..164560876 --- /dev/null +++ b/OpenBCI_GUI/FileBoard.pde @@ -0,0 +1,14 @@ +interface FileBoard { + + public int getTotalSamples(); + + public float getTotalTimeSeconds(); + + public int getCurrentSample(); + + public float getCurrentTimeSeconds(); + + public void goToIndex(int index); + + public boolean endOfFileReached(); +}; diff --git a/OpenBCI_GUI/FilterEnums.pde b/OpenBCI_GUI/FilterEnums.pde new file mode 100644 index 000000000..65cb449fd --- /dev/null +++ b/OpenBCI_GUI/FilterEnums.pde @@ -0,0 +1,70 @@ +public enum BandStopRanges +{ + Sixty(60.0d), + Fifty(50.0d), + None(null); + + private Double freq; + + private static BandStopRanges[] vals = values(); + + BandStopRanges(Double freq) { + this.freq = freq; + } + + public Double getFreq() { + return freq; + } + + public BandStopRanges next() + { + return vals[(this.ordinal() + 1) % vals.length]; + } + + public String getDescr() { + if (freq == null) { + return "None"; + } + return freq.intValue() + "Hz"; + } +} + +public enum BandPassRanges +{ + FiveToFifty(5.0d, 50.0d), + SevenToThirteen(7.0d, 13.0d), + FifteenToFifty(15.0d, 50.0d), + OneToFifty(1.0d, 50.0d), + OneToHundred(1.0d, 100.0d), + None(null, null); + + private Double start; + private Double stop; + + private static BandPassRanges[] vals = values(); + + BandPassRanges(Double start, Double stop) { + this.start = start; + this.stop = stop; + } + + public Double getStart() { + return start; + } + + public Double getStop() { + return stop; + } + + public BandPassRanges next() + { + return vals[(this.ordinal() + 1) % vals.length]; + } + + public String getDescr() { + if ((start == null) || (stop == null)) { + return "None"; + } + return start.intValue() + "-" + stop.intValue() + "Hz"; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/FixedStack.pde b/OpenBCI_GUI/FixedStack.pde new file mode 100644 index 000000000..d6d8e2ad8 --- /dev/null +++ b/OpenBCI_GUI/FixedStack.pde @@ -0,0 +1,35 @@ +import java.util.Stack; + + +public class FixedStack extends Stack { + private int maxSize; + + public FixedStack(int size) { + super(); + this.maxSize = size; + } + + public FixedStack() { + super(); + maxSize = 1000; + } + + // not thread safe with push but its temporary + public void setSize(int size) { + maxSize = size; + } + + public void fill(T object) { + for (int i = 0; i < maxSize; i++) { + push(object); + } + } + + @Override + public T push(T object) { + while (this.size() >= maxSize) { + this.remove(0); + } + return super.push(object); + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/HardwareSettingsController.pde b/OpenBCI_GUI/HardwareSettingsController.pde deleted file mode 100644 index 3dee1989a..000000000 --- a/OpenBCI_GUI/HardwareSettingsController.pde +++ /dev/null @@ -1,381 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// Hardware Settings Controller -// - this is the user interface for allowing you to control the hardware settings of the 32bit Board & 16chan Setup (32bit + Daisy) -// -// Written by: Conor Russomanno (Oct. 2016) ... adapted from ChannelController.pde of GUI V1 ... it's a little bit simpler now :| -// Based on some original GUI code by: Chip Audette 2013/2014 -// -////////////////////////////////////////////////////////////////////////// - -public void updateChannelArrays(int _nchan) { - channelSettingValues = new char [_nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button - impedanceCheckValues = new char [_nchan][2]; -} - -//activateChannel: Ichan is [0 nchan-1] (aka zero referenced) -void activateChannel(int Ichan) { - println("OpenBCI_GUI: activating channel " + (Ichan+1)); - if (eegDataSource == DATASOURCE_CYTON) { - if (cyton.isPortOpen()) { - cyton.changeChannelState(Ichan, true); //activate - } - } else if (eegDataSource == DATASOURCE_GANGLION) { - // println("activating channel on ganglion"); - ganglion.changeChannelState(Ichan, true); - } - if (Ichan < nchan) { - channelSettingValues[Ichan][0] = '0'; - // gui.cc.update(); - } -} -void deactivateChannel(int Ichan) { - println("OpenBCI_GUI: deactivating channel " + (Ichan+1)); - if (eegDataSource == DATASOURCE_CYTON) { - if (cyton.isPortOpen()) { - verbosePrint("**"); - cyton.changeChannelState(Ichan, false); //de-activate - } - } else if (eegDataSource == DATASOURCE_GANGLION) { - ganglion.changeChannelState(Ichan, false); - } - if (Ichan < nchan) { - channelSettingValues[Ichan][0] = '1'; - } -} - -//Ichan is zero referenced (not one referenced) -boolean isChannelActive(int Ichan) { - boolean return_val = false; - if (channelSettingValues[Ichan][0] == '1') { - return_val = false; - } else { - return_val = true; - } - return return_val; -} - -class HardwareSettingsController{ - - boolean isVisible = false; - int x, y, w, h; - - int spaceBetweenButtons = 5; //space between buttons - - // [Number of Channels] x 6 array of buttons for channel settings - Button[][] channelSettingButtons = new Button [nchan][numSettingsPerChannel]; // [channel#][Button#] - - // Array for storing SRB2 history settings of channels prior to shutting off .. so you can return to previous state when reactivating channel - char[] previousSRB2 = new char [nchan]; - // Array for storing SRB2 history settings of channels prior to shutting off .. so you can return to previous state when reactivating channel - char[] previousBIAS = new char [nchan]; - - //maximum different values for the different settings (Power Down, Gain, Input Type, BIAS, SRB2, SRB1) of - //refer to page 44 of ADS1299 Datasheet: http://www.ti.com/lit/ds/symlink/ads1299.pdf - char[] maxValuesPerSetting = { - '1', // Power Down :: (0)ON, (1)OFF - '6', // Gain :: (0) x1, (1) x2, (2) x4, (3) x6, (4) x8, (5) x12, (6) x24 ... default - '7', // Channel Input :: (0)Normal Electrode Input, (1)Input Shorted, (2)Used in conjunction with BIAS_MEAS, (3)MVDD for supply measurement, (4)Temperature Sensor, (5)Test Signal, (6)BIAS_DRP ... positive electrode is driver, (7)BIAS_DRN ... negative electrode is driver - '1', // BIAS :: (0) Yes, (1) No - '1', // SRB2 :: (0) Open, (1) Closed - '1' - }; // SRB1 :: (0) Yes, (1) No ... this setting affects all channels ... either all on or all off - - HardwareSettingsController(int _x, int _y, int _w, int _h, int _channelBarHeight){ - x = _x; - y = _y; - w = _w; - h = _h; - - createChannelSettingButtons(_channelBarHeight); - } - - void update(){ - for (int i = 0; i < nchan; i++) { //for every channel - //update buttons based on channelSettingValues[i][j] - for (int j = 0; j < numSettingsPerChannel; j++) { - switch(j) { //what setting are we looking at - case 0: //on/off ?? - // if (channelSettingValues[i][j] == '0') channelSettingButtons[i][0].setColorNotPressed(channelColors[i%8]);// power down == false, set color to vibrant - if (channelSettingValues[i][j] == '0') w_timeSeries.channelBars[i].onOffButton.setColorNotPressed(channelColors[i%8]);// power down == false, set color to vibrant - if (channelSettingValues[i][j] == '1') w_timeSeries.channelBars[i].onOffButton.setColorNotPressed(75); // power down == true, set color to dark gray, indicating power down - break; - case 1: //GAIN ?? - if (channelSettingValues[i][j] == '0') channelSettingButtons[i][1].setString("x1"); - if (channelSettingValues[i][j] == '1') channelSettingButtons[i][1].setString("x2"); - if (channelSettingValues[i][j] == '2') channelSettingButtons[i][1].setString("x4"); - if (channelSettingValues[i][j] == '3') channelSettingButtons[i][1].setString("x6"); - if (channelSettingValues[i][j] == '4') channelSettingButtons[i][1].setString("x8"); - if (channelSettingValues[i][j] == '5') channelSettingButtons[i][1].setString("x12"); - if (channelSettingValues[i][j] == '6') channelSettingButtons[i][1].setString("x24"); - break; - case 2: //input type ?? - if (channelSettingValues[i][j] == '0') channelSettingButtons[i][2].setString("Normal"); - if (channelSettingValues[i][j] == '1') channelSettingButtons[i][2].setString("Shorted"); - if (channelSettingValues[i][j] == '2') channelSettingButtons[i][2].setString("BIAS_MEAS"); - if (channelSettingValues[i][j] == '3') channelSettingButtons[i][2].setString("MVDD"); - if (channelSettingValues[i][j] == '4') channelSettingButtons[i][2].setString("Temp."); - if (channelSettingValues[i][j] == '5') channelSettingButtons[i][2].setString("Test"); - if (channelSettingValues[i][j] == '6') channelSettingButtons[i][2].setString("BIAS_DRP"); - if (channelSettingValues[i][j] == '7') channelSettingButtons[i][2].setString("BIAS_DRN"); - break; - case 3: //BIAS ?? - if (channelSettingValues[i][j] == '0') channelSettingButtons[i][3].setString("Don't Include"); - if (channelSettingValues[i][j] == '1') channelSettingButtons[i][3].setString("Include"); - break; - case 4: // SRB2 ?? - if (channelSettingValues[i][j] == '0') channelSettingButtons[i][4].setString("Off"); - if (channelSettingValues[i][j] == '1') channelSettingButtons[i][4].setString("On"); - break; - case 5: // SRB1 ?? - if (channelSettingValues[i][j] == '0') channelSettingButtons[i][5].setString("No"); - if (channelSettingValues[i][j] == '1') channelSettingButtons[i][5].setString("Yes"); - break; - } - } - } - } - - void draw(){ - pushStyle(); - - if (isVisible) { - //background - noStroke(); - fill(0, 0, 0, 100); - rect(x, y, w, h); - - // [numChan] x 5 ... all channel setting buttons (other than on/off) - for (int i = 0; i < nchan; i++) { - for (int j = 1; j < 6; j++) { - channelSettingButtons[i][j].draw(); - } - } - - //draw column headers for channel settings behind EEG graph - fill(bgColor); - textFont(p6, 10); - textAlign(CENTER, TOP); - text("PGA Gain", x + (w/10)*1, y-1); - text("Input Type", x + (w/10)*3, y-1); - text(" Bias ", x + (w/10)*5, y-1); - text("SRB2", x + (w/10)*7, y-1); - text("SRB1", x + (w/10)*9, y-1); - - //if mode is not from OpenBCI, draw a dark overlay to indicate that you cannot edit these settings - if (eegDataSource != DATASOURCE_CYTON) { - fill(0, 0, 0, 200); - noStroke(); - rect(x-2, y, w+1, h); - fill(255); - textAlign(CENTER,CENTER); - textFont(h1,18); - text("DATA SOURCE (LIVE) only", x + (w/2), y + (h/2)); - } - } - popStyle(); - } - - public void loadDefaultChannelSettings() { - for (int i = 0; i < nchan; i++) { - channelSettingValues[i][0] = '0'; - channelSettingValues[i][1] = '6'; - channelSettingValues[i][2] = '0'; - channelSettingValues[i][3] = '1'; - channelSettingValues[i][4] = '1'; - channelSettingValues[i][5] = '0'; - - for (int k = 0; k < 2; k++) { //impedance setting values - impedanceCheckValues[i][k] = '0'; - } - } - update(); //update 1 time to refresh button values based on new loaded settings - } - - //activateChannel: Ichan is [0 nchan-1] (aka zero referenced) - void activateChannel(int Ichan) { - println("OpenBCI_GUI: activating channel " + (Ichan+1)); - if (eegDataSource == DATASOURCE_CYTON) { - if (cyton.isPortOpen()) { - verbosePrint("**"); - cyton.changeChannelState(Ichan, true); //activate - } - } else if (eegDataSource == DATASOURCE_GANGLION) { - ganglion.changeChannelState(Ichan, true); - } - if (Ichan < nchan) { - channelSettingValues[Ichan][0] = '0'; - w_timeSeries.hsc.update(); //previously gui.cc.update(); - } - } - - void deactivateChannel(int Ichan) { - println("OpenBCI_GUI: deactivating channel " + (Ichan+1)); - if (eegDataSource == DATASOURCE_CYTON) { - if (cyton.isPortOpen()) { - verbosePrint("**"); - cyton.changeChannelState(Ichan, false); //de-activate - } - } else if (eegDataSource == DATASOURCE_GANGLION) { - // println("deactivating channel on ganglion"); - ganglion.changeChannelState(Ichan, false); - } - if (Ichan < nchan) { - channelSettingValues[Ichan][0] = '1'; - w_timeSeries.hsc.update(); - } - } - - //Ichan is zero referenced (not one referenced) - boolean isChannelActive(int Ichan) { - boolean return_val = false; - if (channelSettingValues[Ichan][0] == '1') { - return_val = false; - } else { - return_val = true; - } - return return_val; - } - - public void powerDownChannel(int _numChannel) { - verbosePrint("Powering down channel " + str(int(_numChannel) + int(1))); - //save SRB2 and BIAS settings in 2D history array (to turn back on when channel is reactivated) - previousBIAS[_numChannel] = channelSettingValues[_numChannel][3]; - previousSRB2[_numChannel] = channelSettingValues[_numChannel][4]; - channelSettingValues[_numChannel][3] = '0'; //make sure to disconnect from BIAS - channelSettingValues[_numChannel][4] = '0'; //make sure to disconnect from SRB2 - - channelSettingValues[_numChannel][0] = '1'; //update powerUp/powerDown value of 2D array - verbosePrint("Command: " + command_deactivate_channel[_numChannel]); - cyton.deactivateChannel(_numChannel); //assumes numChannel counts from zero (not one)...handles regular and daisy channels - } - - public void powerUpChannel(int _numChannel) { - verbosePrint("Powering up channel " + str(int(_numChannel) + int(1))); - //replace SRB2 and BIAS settings with values from 2D history array - channelSettingValues[_numChannel][3] = previousBIAS[_numChannel]; - channelSettingValues[_numChannel][4] = previousSRB2[_numChannel]; - - channelSettingValues[_numChannel][0] = '0'; //update powerUp/powerDown value of 2D array - verbosePrint("Command: " + command_activate_channel[_numChannel]); - cyton.activateChannel(_numChannel); //assumes numChannel counts from zero (not one)...handles regular and daisy channels//assumes numChannel counts from zero (not one)...handles regular and daisy channels - } - - public void initImpWrite(int _numChannel, char pORn, char onORoff) { - verbosePrint("Writing impedance check settings (" + pORn + "," + onORoff + ") for channel " + str(_numChannel) + " to OpenBCI!"); - if (pORn == 'p') { - impedanceCheckValues[_numChannel-1][0] = onORoff; - } - if (pORn == 'n') { - impedanceCheckValues[_numChannel-1][1] = onORoff; - } - cyton.writeImpedanceSettings(_numChannel, impedanceCheckValues); - } - - public void createChannelSettingButtons(int _channelBarHeight) { - //the size and space of these buttons are dependendant on the size of the screen and full ChannelController - verbosePrint("ChannelController: createChannelSettingButtons: creating channel setting buttons..."); - int buttonW = 0; - int buttonX = 0; - int buttonH = 0; - int buttonY = 0; //variables to be used for button creation below - String buttonString = ""; - Button tempButton; - - for (int i = 0; i < nchan; i++) { - for (int j = 1; j < 6; j++) { - buttonW = int((w - (spaceBetweenButtons*6)) / 5); - buttonX = int((x + (spaceBetweenButtons * (j))) + ((j-1) * buttonW)); - buttonH = 18; - // buttonY = int(y + ((30)*i) + (((30)-buttonH)/2)); //timeSeries_widget.channelBarHeight - buttonY = int(y + ((_channelBarHeight)*i) + (((_channelBarHeight)-buttonH)/2)); //timeSeries_widget.channelBarHeight - buttonString = "N/A"; - tempButton = new Button (buttonX, buttonY, buttonW, buttonH, buttonString, 14); - channelSettingButtons[i][j] = tempButton; - } - } - } - - void mousePressed(){ - if (isVisible) { - for (int i = 0; i < nchan; i++) { //When [i][j] button is clicked - for (int j = 1; j < numSettingsPerChannel; j++) { - if (channelSettingButtons[i][j].isMouseHere()) { - //increment [i][j] channelSettingValue by, until it reaches max values per setting [j], - channelSettingButtons[i][j].wasPressed = true; - channelSettingButtons[i][j].isActive = true; - } - } - } - } - } - - void mouseReleased(){ - if (isVisible) { - for (int i = 0; i < nchan; i++) { //When [i][j] button is clicked - for (int j = 1; j < numSettingsPerChannel; j++) { - if (channelSettingButtons[i][j].isMouseHere() && channelSettingButtons[i][j].wasPressed == true) { - if (channelSettingValues[i][j] < maxValuesPerSetting[j]) { - channelSettingValues[i][j]++; //increment [i][j] channelSettingValue by, until it reaches max values per setting [j], - } else { - channelSettingValues[i][j] = '0'; - } - cyton.writeChannelSettings(i, channelSettingValues); - } - - // if(!channelSettingButtons[i][j].isMouseHere()){ - channelSettingButtons[i][j].isActive = false; - channelSettingButtons[i][j].wasPressed = false; - // } - } - } - } - } - - void screenResized(int _x, int _y, int _w, int _h, int _channelBarHeight){ - x = _x; - y = _y; - w = _w; - h = _h; - - int buttonW = 0; - int buttonX = 0; - int buttonH = 0; - int buttonY = 0; //variables to be used for button creation below - String buttonString = ""; - - for (int i = 0; i < nchan; i++) { - for (int j = 1; j < 6; j++) { - buttonW = int((w - (spaceBetweenButtons*6)) / 5); - buttonX = int((x + (spaceBetweenButtons * (j))) + ((j-1) * buttonW)); - buttonH = 18; - buttonY = int(y + ((_channelBarHeight)*i) + (((_channelBarHeight)-buttonH)/2)); //timeSeries_widget.channelBarHeight - buttonString = "N/A"; - channelSettingButtons[i][j].but_x = buttonX; - channelSettingButtons[i][j].but_y = buttonY; - channelSettingButtons[i][j].but_dx = buttonW; - channelSettingButtons[i][j].but_dy = buttonH; - } - } - } - - void toggleImpedanceCheck(int _channelNumber){ //Channel Numbers start at 1 - if(channelSettingValues[_channelNumber-1][4] == '1'){ //is N pin being used... - if (impedanceCheckValues[_channelNumber-1][1] < '1') { //if not checking/drawing impedance - initImpWrite(_channelNumber, 'n', '1'); // turn on the impedance check for the desired channel - println("Imp[" + _channelNumber + "] is on."); - } else { - initImpWrite(_channelNumber, 'n', '0'); //turn off impedance check for desired channel - println("Imp[" + _channelNumber + "] is off."); - } - } - - if(channelSettingValues[_channelNumber-1][4] == '0'){ //is P pin being used - if (impedanceCheckValues[_channelNumber-1][0] < '1') { //is channel on - initImpWrite(_channelNumber, 'p', '1'); - } else { - initImpWrite(_channelNumber, 'p', '0'); - } - } - } -}; diff --git a/OpenBCI_GUI/ImpedanceSettingsBoard.pde b/OpenBCI_GUI/ImpedanceSettingsBoard.pde new file mode 100644 index 000000000..cfdde8410 --- /dev/null +++ b/OpenBCI_GUI/ImpedanceSettingsBoard.pde @@ -0,0 +1,6 @@ + +interface ImpedanceSettingsBoard { + + public void setCheckingImpedance(int channel, boolean active); + public boolean isCheckingImpedance(int channel); +}; diff --git a/OpenBCI_GUI/Info.plist.tmpl b/OpenBCI_GUI/Info.plist.tmpl index 63c49a154..9ae859332 100644 --- a/OpenBCI_GUI/Info.plist.tmpl +++ b/OpenBCI_GUI/Info.plist.tmpl @@ -23,7 +23,7 @@ CFBundleShortVersionString 4 CFBundleVersion - 4.2.0 + 5.0.0 CFBundleSignature ???? NSHumanReadableCopyright @@ -32,7 +32,7 @@ Copyright © 2020 OpenBCI CFBundleGetInfoString - January 2020 + August 2020 @@jvm_runtime@@ diff --git a/OpenBCI_GUI/Interactivity.pde b/OpenBCI_GUI/Interactivity.pde index 6fe32116a..df328cc3d 100644 --- a/OpenBCI_GUI/Interactivity.pde +++ b/OpenBCI_GUI/Interactivity.pde @@ -27,11 +27,9 @@ synchronized void keyPressed() { //println("OpenBCI_GUI: keyPressed: key = " + key + ", int(key) = " + int(key) + ", keyCode = " + keyCode); if(!controlPanel.isOpen && !isNetworkingTextActive()){ //don't parse the key if the control panel is open - if (settings.expertModeToggle || (int(key) == 32)) { //Check if Expert Mode is On or Spacebar has been pressed + if (settings.expertModeToggle || key == ' ') { //Check if Expert Mode is On or Spacebar has been pressed if ((int(key) >=32) && (int(key) <= 126)) { //32 through 126 represent all the usual printable ASCII characters parseKey(key); - } else { - parseKeycode(keyCode); } } } @@ -42,29 +40,15 @@ synchronized void keyPressed() { } void parseKey(char val) { - int Ichan; boolean activate; int code_P_N_Both; - //assumes that val is a usual printable ASCII character (ASCII 32 through 126) switch (val) { case ' ': + // space to start/stop the stream stopButtonWasPressed(); break; - case '.': - //This keyboard shortcut is not being used! - break; case ',': drawContainers = !drawContainers; break; - case '<': - //w_timeSeries.setUpdating(!w_timeSeries.isUpdating()); - break; - case '>': - /* - if(eegDataSource == DATASOURCE_GANGLION){ - ganglion.enterBootloaderMode(); - } - */ - break; case '{': if(colorScheme == COLOR_SCHEME_DEFAULT){ colorScheme = COLOR_SCHEME_ALTERNATIVE_A; @@ -74,181 +58,118 @@ void parseKey(char val) { topNav.updateNavButtonsBasedOnColorScheme(); println("Changing color scheme."); break; - case '/': - //Not being used - break; - case '\\': - //Not being used - break; + + //deactivate channels 1-16 case '1': - deactivateChannel(1-1); + currentBoard.setEXGChannelActive(1-1, false); break; case '2': - deactivateChannel(2-1); + currentBoard.setEXGChannelActive(2-1, false); break; case '3': - deactivateChannel(3-1); + currentBoard.setEXGChannelActive(3-1, false); break; case '4': - deactivateChannel(4-1); + currentBoard.setEXGChannelActive(4-1, false); break; case '5': - deactivateChannel(5-1); + currentBoard.setEXGChannelActive(5-1, false); break; case '6': - deactivateChannel(6-1); + currentBoard.setEXGChannelActive(6-1, false); break; case '7': - deactivateChannel(7-1); + currentBoard.setEXGChannelActive(7-1, false); break; case '8': - deactivateChannel(8-1); + currentBoard.setEXGChannelActive(8-1, false); break; - case 'q': - if(nchan == 16){ - deactivateChannel(9-1); - } + currentBoard.setEXGChannelActive(9-1, false); break; case 'w': - if(nchan == 16){ - deactivateChannel(10-1); - } + currentBoard.setEXGChannelActive(10-1, false); break; case 'e': - if(nchan == 16){ - deactivateChannel(11-1); - } + currentBoard.setEXGChannelActive(11-1, false); break; case 'r': - if(nchan == 16){ - deactivateChannel(12-1); - } + currentBoard.setEXGChannelActive(12-1, false); break; case 't': - if(nchan == 16){ - deactivateChannel(13-1); - } + currentBoard.setEXGChannelActive(13-1, false); break; case 'y': - if(nchan == 16){ - deactivateChannel(14-1); - } + currentBoard.setEXGChannelActive(14-1, false); break; case 'u': - if(nchan == 16){ - deactivateChannel(15-1); - } + currentBoard.setEXGChannelActive(15-1, false); break; case 'i': - if(nchan == 16){ - deactivateChannel(16-1); - } - break; - case ':': - println("test..."); //@@@@@ - boolean test = isNetworkingTextActive(); + currentBoard.setEXGChannelActive(16-1, false); break; - //activate channels 1-8 + //activate channels 1-16 case '!': - activateChannel(1-1); + currentBoard.setEXGChannelActive(1-1, true); break; case '@': - activateChannel(2-1); + currentBoard.setEXGChannelActive(2-1, true); break; case '#': - activateChannel(3-1); + currentBoard.setEXGChannelActive(3-1, true); break; case '$': - activateChannel(4-1); + currentBoard.setEXGChannelActive(4-1, true); break; case '%': - activateChannel(5-1); + currentBoard.setEXGChannelActive(5-1, true); break; case '^': - activateChannel(6-1); + currentBoard.setEXGChannelActive(6-1, true); break; case '&': - activateChannel(7-1); + currentBoard.setEXGChannelActive(7-1, true); break; case '*': - activateChannel(8-1); + currentBoard.setEXGChannelActive(8-1, true); break; - - //activate channels 9-16 (DAISY MODE ONLY) case 'Q': - if(nchan == 16){ - activateChannel(9-1); - } + currentBoard.setEXGChannelActive(9-1, true); break; case 'W': - if(nchan == 16){ - activateChannel(10-1); - } + currentBoard.setEXGChannelActive(10-1, true); break; case 'E': - if(nchan == 16){ - activateChannel(11-1); - } + currentBoard.setEXGChannelActive(11-1, true); break; case 'R': - if(nchan == 16){ - activateChannel(12-1); - } + currentBoard.setEXGChannelActive(12-1, true); break; case 'T': - if(nchan == 16){ - activateChannel(13-1); - } + currentBoard.setEXGChannelActive(13-1, true); break; case 'Y': - if(nchan == 16){ - activateChannel(14-1); - } + currentBoard.setEXGChannelActive(14-1, true); break; case 'U': - if(nchan == 16){ - activateChannel(15-1); - } + currentBoard.setEXGChannelActive(15-1, true); break; case 'I': - if(nchan == 16){ - activateChannel(16-1); - } + currentBoard.setEXGChannelActive(16-1, true); break; //other controls case 's': - println("case s..."); stopRunning(); //stopButtonWasPressed(); break; case 'b': - println("case b..."); startRunning(); //stopButtonWasPressed(); break; - //Lowercase k sets Bias Don't Include all channels - case 'k': - for (int i = 0; i < nchan; i++) { //for every channel - //BIAS off all channels - channelSettingValues[i][3] = '0'; - println ("chan " + i + " bias don't include"); - } - break; - //Lowercase l sets Bias Include all channels - case 'l': - for (int i = 0; i < nchan; i++) { //for every channel - //buttons are updated in HardwareSettingsController based on channelSettingValues[i][j] - //BIAS on all channells - channelSettingValues[i][3] = '1'; - println ("chan " + i + " bias include"); - } - break; - ///////////////////// Save User settings lowercase n case 'n': println("Save key pressed!"); @@ -263,156 +184,30 @@ void parseKey(char val) { break; case '?': - cyton.printRegisters(); + if(currentBoard instanceof BoardCyton) { + ((BoardCyton)currentBoard).printRegisters(); + } break; - case 'd': - verbosePrint("Updating GUI's channel settings to default..."); - w_timeSeries.hsc.loadDefaultChannelSettings(); - //cyton.serial_openBCI.write('d'); - cyton.configureAllChannelsToDefault(); + case 'd': break; case 'm': - String picfname = "OpenBCI-" + getDateString() + ".jpg"; + String picfname = "OpenBCI-" + directoryManager.getFileNameDateTime() + ".jpg"; //println("OpenBCI_GUI: 'm' was pressed...taking screenshot:" + picfname); - saveFrame(settings.guiDataPath + "Screenshots" + System.getProperty("file.separator") + picfname); // take a shot of that! + saveFrame(directoryManager.getGuiDataPath() + "Screenshots" + System.getProperty("file.separator") + picfname); // take a shot of that! output("Screenshot captured! Saved to /Documents/OpenBCI_GUI/Screenshots/" + picfname); break; - /* - //Used for testing marker mode - case 'M': - if (eegDataSource == DATASOURCE_CYTON) { - hub.sendCommand("`9"); - println("Cyton: Setting a Marker +++++"); - } - break; - */ + default: - if (eegDataSource == DATASOURCE_CYTON) { - println("Interactivity: '" + key + "' Pressed...sending to Cyton..."); - cyton.write(key); - } else if (eegDataSource == DATASOURCE_GANGLION) { - println("Interactivity: '" + key + "' Pressed...sending to Ganglion..."); - hub.sendCommand(key); + if (currentBoard instanceof Board) { + println("Interactivity: '" + key + "' Pressed...sending to Board..."); + ((Board)currentBoard).sendCommand(str(key)); } break; } } -void parseKeycode(int val) { - //assumes that val is Java keyCode - switch (val) { - case 8: - println("OpenBCI_GUI: parseKeycode(" + val + "): received BACKSPACE keypress. Ignoring..."); - break; - case 9: - println("OpenBCI_GUI: parseKeycode(" + val + "): received TAB keypress. Ignoring..."); - //gui.showImpedanceButtons = !gui.showImpedanceButtons; - // gui.incrementGUIpage(); //deprecated with new channel controller - break; - case 10: - println("Enter was pressed."); - drawPresentation = !drawPresentation; - break; - case 16: - println("OpenBCI_GUI: parseKeycode(" + val + "): received SHIFT keypress. Ignoring..."); - break; - case 17: - //println("OpenBCI_GUI: parseKeycode(" + val + "): received CTRL keypress. Ignoring..."); - break; - case 18: - println("OpenBCI_GUI: parseKeycode(" + val + "): received ALT keypress. Ignoring..."); - break; - case 20: - println("OpenBCI_GUI: parseKeycode(" + val + "): received CAPS LOCK keypress. Ignoring..."); - break; - case 27: - println("OpenBCI_GUI: parseKeycode(" + val + "): received ESC keypress. Stopping OpenBCI..."); - //stopRunning(); - break; - case 33: - println("OpenBCI_GUI: parseKeycode(" + val + "): received PAGE UP keypress. Ignoring..."); - break; - case 34: - println("OpenBCI_GUI: parseKeycode(" + val + "): received PAGE DOWN keypress. Ignoring..."); - break; - case 35: - println("OpenBCI_GUI: parseKeycode(" + val + "): received END keypress. Ignoring..."); - break; - case 36: - println("OpenBCI_GUI: parseKeycode(" + val + "): received HOME keypress. Ignoring..."); - break; - case 37: - if (millis() - myPresentation.timeOfLastSlideChange >= 250) { - if(myPresentation.currentSlide >= 0){ - myPresentation.slideBack(); - myPresentation.timeOfLastSlideChange = millis(); - } - } - break; - case 38: - println("OpenBCI_GUI: parseKeycode(" + val + "): received UP ARROW keypress. Ignoring..."); - break; - case 39: - if (millis() - myPresentation.timeOfLastSlideChange >= 250) { - if(myPresentation.currentSlide < myPresentation.presentationSlides.length - 1){ - myPresentation.slideForward(); - myPresentation.timeOfLastSlideChange = millis(); - } - } - break; - case 40: - println("OpenBCI_GUI: parseKeycode(" + val + "): received DOWN ARROW keypress. Ignoring..."); - break; - case 112: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F1 keypress. Ignoring..."); - break; - case 113: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F2 keypress. Ignoring..."); - break; - case 114: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F3 keypress. Ignoring..."); - break; - case 115: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F4 keypress. Ignoring..."); - break; - case 116: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F5 keypress. Ignoring..."); - break; - case 117: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F6 keypress. Ignoring..."); - break; - case 118: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F7 keypress. Ignoring..."); - break; - case 119: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F8 keypress. Ignoring..."); - break; - case 120: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F9 keypress. Ignoring..."); - break; - case 121: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F10 keypress. Ignoring..."); - break; - case 122: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F11 keypress. Ignoring..."); - break; - case 123: - println("OpenBCI_GUI: parseKeycode(" + val + "): received F12 keypress. Ignoring..."); - break; - case 127: - println("OpenBCI_GUI: parseKeycode(" + val + "): received DELETE keypress. Ignoring..."); - break; - case 155: - println("OpenBCI_GUI: parseKeycode(" + val + "): received INSERT keypress. Ignoring..."); - break; - default: - println("OpenBCI_GUI: parseKeycode(" + val + "): value is not known. Ignoring..."); - break; - } -} - void mouseDragged() { if (systemMode >= SYSTEMMODE_POSTINIT) { @@ -464,8 +259,6 @@ synchronized void mousePressed() { } } } - - redrawScreenNow = true; //command a redraw of the GUI whenever the mouse is pressed } synchronized void mouseReleased() { @@ -490,8 +283,6 @@ synchronized void mouseReleased() { // GUIWidgets_mouseReleased(); // to replace GUI_Manager version (above) soon... cdr 7/25/16 wm.mouseReleased(); - - redrawScreenNow = true; //command a redraw of the GUI whenever the mouse is released } if (settings.screenHasBeenResized) { @@ -504,20 +295,51 @@ synchronized void mouseReleased() { // Classes //------------------------------------------------------------------------ +class CustomScrollableList extends ScrollableList { -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Formerly Button.pde -// This class creates and manages a button for use on the screen to trigger actions. -// -// Created: Chip Audette, Oct 2013. -// Modified: Conor Russomanno, Oct 2014 -// -// Based on Processing's "Button" example code -// -//////////////////////////////////////////////////////////////////////////////////////////////////// + CustomScrollableList(ControlP5 cp5, String name) { + super(cp5, name); + } + + // there's a bug in control p5 where clicking on the scroll list does not + // open it if you move the mouse while clicking. This fixes that. + @Override + protected void onEndDrag() { + super.onEndDrag(); + setOpen(!isOpen()); + } + + // close the dropdown if the mouse leaves it. + @Override + protected void onLeave() { + super.onLeave(); + close(); + } + + @Override + public ScrollableList updateDisplayMode( int theMode ) { + super.updateDisplayMode(theMode); -class Button { + if (theMode == DEFAULT) { + _myControllerView = new CustomScrollableListView( ); + } + + return this; + } + + public class CustomScrollableListView extends ScrollableListView { + @Override + public void display(PGraphics g , ScrollableList c) { + // draw rect behind the dropdown + fill(c.getBackgroundColor()); + rect(-1, -1, c.getWidth()+2, c.getHeight()+2); + + super.display(g, c); + } + } +} + +class Button_obci { int but_x, but_y, but_dx, but_dy; // Position of square button //int rectSize = 90; // Diameter of rect @@ -548,13 +370,13 @@ class Button { boolean hasbgImage = false; private boolean ignoreHover = false; - public Button(int x, int y, int w, int h, String txt) { + public Button_obci(int x, int y, int w, int h, String txt) { setup(x, y, w, h, txt); buttonFont = p5; buttonTextSize = 12; } - public Button(int x, int y, int w, int h, String txt, int fontSize) { + public Button_obci(int x, int y, int w, int h, String txt, int fontSize) { setup(x, y, w, h, txt); buttonFont = p5; buttonTextSize = 12; @@ -613,7 +435,7 @@ class Button { public void setString(String txt) { but_txt = txt; - //println("Button: setString: string = " + txt); + //println("Button_obci: setString: string = " + txt); } public void setHelpText(String _helpText){ @@ -696,10 +518,6 @@ class Button { return but_txt; } - public void setCurrentColor(color _color){ - currentColor = _color; - } - public void setColorPressed(color _color) { color_pressed = _color; } @@ -888,23 +706,24 @@ void toggleFrameRate(){ frameRateCounter = 1; // until we resolve the latency issue with 24hz, only allow 30hz minimum (aka frameRateCounter = 1) } if(frameRateCounter==0){ - frameRate(24); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate - topNav.fpsButton.setString("24 fps"); + setFrameRate(24); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate } if(frameRateCounter==1){ - frameRate(30); - topNav.fpsButton.setString("30 fps"); + setFrameRate(30); } if(frameRateCounter==2){ - frameRate(45); - topNav.fpsButton.setString("45 fps"); + setFrameRate(45); } if(frameRateCounter==3){ - frameRate(60); - topNav.fpsButton.setString("60 fps"); + setFrameRate(60); } } +void setFrameRate(int fps) { + frameRate(fps); + topNav.fpsButton.setString(fps + " fps"); +} + //loop through networking textfields and find out if any are active boolean isNetworkingTextActive(){ boolean isAFieldActive = false; diff --git a/OpenBCI_GUI/InterfaceHub.pde b/OpenBCI_GUI/InterfaceHub.pde deleted file mode 100644 index de5efbc0d..000000000 --- a/OpenBCI_GUI/InterfaceHub.pde +++ /dev/null @@ -1,1335 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// -// This class configures and manages the connection to the OpenBCI Ganglion. -// The connection is implemented via a TCP connection to a TCP port. -// The Gagnlion is configured using single letter text commands sent from the -// PC to the TCP server. The EEG data streams back from the Ganglion, to the -// TCP server and back to the PC continuously (once started). -// -// Created: AJ Keller, August 2016 -// -///////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------ -// Global Functions -//------------------------------------------------------------------------ - -int numPacketsDroppedHub = 0; -final boolean debugRandomlyDropPackets = false; // SET THIS TO FALSE BEFORE SHIPPING - -final static String TCP_JSON_KEY_ACTION = "action"; -final static String TCP_JSON_KEY_ACCEL_DATA_COUNTS = "accelDataCounts"; -final static String TCP_JSON_KEY_AUX_DATA = "auxData"; -final static String TCP_JSON_KEY_BOARD_TYPE = "boardType"; -final static String TCP_JSON_KEY_BURST_MODE = "burst"; -final static String TCP_JSON_KEY_CHANNEL_DATA_COUNTS = "channelDataCounts"; -final static String TCP_JSON_KEY_CHANNEL_NUMBER = "channelNumber"; -final static String TCP_JSON_KEY_CHANNEL_SET_CHANNEL_NUMBER = "channelNumber"; -final static String TCP_JSON_KEY_CHANNEL_SET_POWER_DOWN = "powerDown"; -final static String TCP_JSON_KEY_CHANNEL_SET_GAIN = "gain"; -final static String TCP_JSON_KEY_CHANNEL_SET_INPUT_TYPE = "inputType"; -final static String TCP_JSON_KEY_CHANNEL_SET_BIAS = "bias"; -final static String TCP_JSON_KEY_CHANNEL_SET_SRB2 = "srb2"; -final static String TCP_JSON_KEY_CHANNEL_SET_SRB1 = "srb1"; -final static String TCP_JSON_KEY_CODE = "code"; -final static String TCP_JSON_KEY_COMMAND = "command"; -final static String TCP_JSON_KEY_DATA = "data"; -final static String TCP_JSON_KEY_FIRMWARE = "firmware"; -final static String TCP_JSON_KEY_IMPEDANCE_VALUE = "impedanceValue"; -final static String TCP_JSON_KEY_IMPEDANCE_SET_P_INPUT = "pInputApplied"; -final static String TCP_JSON_KEY_IMPEDANCE_SET_N_INPUT = "nInputApplied"; -final static String TCP_JSON_KEY_LATENCY = "latency"; -final static String TCP_JSON_KEY_LOWER = "lower"; -final static String TCP_JSON_KEY_MESSAGE = "message"; -final static String TCP_JSON_KEY_NAME = "name"; -final static String TCP_JSON_KEY_PROTOCOL = "protocol"; -final static String TCP_JSON_KEY_SAMPLE_NUMBER = "sampleNumber"; -final static String TCP_JSON_KEY_SAMPLE_RATE = "sampleRate"; -final static String TCP_JSON_KEY_SHIELD_NAME = "shieldName"; -final static String TCP_JSON_KEY_STOP_BYTE = "stopByte"; -final static String TCP_JSON_KEY_TIMESTAMP = "timestamp"; -final static String TCP_JSON_KEY_TYPE = "type"; - -final static String TCP_TYPE_ACCEL = "accelerometer"; -final static String TCP_TYPE_BOARD_TYPE = "boardType"; -final static String TCP_TYPE_CHANNEL_SETTINGS = "channelSettings"; -final static String TCP_TYPE_COMMAND = "command"; -final static String TCP_TYPE_CONNECT = "connect"; -final static String TCP_TYPE_DISCONNECT = "disconnect"; -final static String TCP_TYPE_DATA = "data"; -final static String TCP_TYPE_ERROR = "error"; -final static String TCP_TYPE_EXAMINE = "examine"; -final static String TCP_TYPE_IMPEDANCE = "impedance"; -final static String TCP_TYPE_LOG = "log"; -final static String TCP_TYPE_PROTOCOL = "protocol"; -final static String TCP_TYPE_SCAN = "scan"; -final static String TCP_TYPE_SD = "sd"; -final static String TCP_TYPE_STATUS = "status"; -final static String TCP_TYPE_WIFI = "wifi"; -final static String TCP_STOP = "\r\n"; - -final static String TCP_ACTION_SET = "set"; -final static String TCP_ACTION_START = "start"; -final static String TCP_ACTION_STATUS = "status"; -final static String TCP_ACTION_STOP = "stop"; - - -final static String TCP_WIFI_ERASE_CREDENTIALS = "eraseCredentials"; -final static String TCP_WIFI_GET_FIRMWARE_VERSION = "getFirmwareVersion"; -final static String TCP_WIFI_GET_IP_ADDRESS = "getIpAddress"; -final static String TCP_WIFI_GET_MAC_ADDRESS = "getMacAddress"; -final static String TCP_WIFI_GET_TYPE_OF_ATTACHED_BOARD = "getTypeOfAttachedBoard"; - -final static byte BYTE_START = (byte)0xA0; -final static byte BYTE_END = (byte)0xC0; - -// States For Syncing with the hardware -enum HubState { - NOCOM, - COMINIT, - SYNCWITHHARDWARE, - NORMAL, - STOPPED -} - -final static int STATE_STOPPED = 4; -final static int COM_INIT_MSEC = 3000; //you may need to vary this for your computer or your Arduino - -final static int NUM_ACCEL_DIMS = 3; - -final static int RESP_ERROR_UNKNOWN = 499; -final static int RESP_ERROR_ALREADY_CONNECTED = 408; -final static int RESP_ERROR_BAD_PACKET = 500; -final static int RESP_ERROR_BAD_NOBLE_START = 501; -final static int RESP_ERROR_CHANNEL_SETTINGS = 423; -final static int RESP_ERROR_CHANNEL_SETTINGS_SYNC_IN_PROGRESS = 422; -final static int RESP_ERROR_CHANNEL_SETTINGS_FAILED_TO_SET_CHANNEL = 424; -final static int RESP_ERROR_CHANNEL_SETTINGS_FAILED_TO_PARSE = 425; -final static int RESP_ERROR_COMMAND_NOT_ABLE_TO_BE_SENT = 406; -final static int RESP_ERROR_COMMAND_NOT_RECOGNIZED = 434; -final static int RESP_ERROR_DEVICE_NOT_FOUND = 405; -final static int RESP_ERROR_IMPEDANCE_COULD_NOT_START = 414; -final static int RESP_ERROR_IMPEDANCE_COULD_NOT_STOP = 415; -final static int RESP_ERROR_IMPEDANCE_FAILED_TO_SET_IMPEDANCE = 430; -final static int RESP_ERROR_IMPEDANCE_FAILED_TO_PARSE = 431; -final static int RESP_ERROR_NO_OPEN_BLE_DEVICE = 400; -final static int RESP_ERROR_UNABLE_TO_CONNECT = 402; -final static int RESP_ERROR_UNABLE_TO_DISCONNECT = 401; -final static int RESP_ERROR_PROTOCOL_UNKNOWN = 418; -final static int RESP_ERROR_PROTOCOL_BLE_START = 419; -final static int RESP_ERROR_PROTOCOL_NOT_STARTED = 420; -final static int RESP_ERROR_UNABLE_TO_SET_BOARD_TYPE = 421; -final static int RESP_ERROR_SCAN_ALREADY_SCANNING = 409; -final static int RESP_ERROR_SCAN_NONE_FOUND = 407; -final static int RESP_ERROR_SCAN_NO_SCAN_TO_STOP = 410; -final static int RESP_ERROR_SCAN_COULD_NOT_START = 412; -final static int RESP_ERROR_SCAN_COULD_NOT_STOP = 411; -final static int RESP_ERROR_TIMEOUT_SCAN_STOPPED = 432; -final static int RESP_ERROR_WIFI_ACTION_NOT_RECOGNIZED = 427; -final static int RESP_ERROR_WIFI_COULD_NOT_ERASE_CREDENTIALS = 428; -final static int RESP_ERROR_WIFI_COULD_NOT_SET_LATENCY = 429; -final static int RESP_ERROR_WIFI_NEEDS_UPDATE = 435; -final static int RESP_ERROR_WIFI_NOT_CONNECTED = 426; -final static int RESP_GANGLION_FOUND = 201; -final static int RESP_SUCCESS = 200; -final static int RESP_SUCCESS_DATA_ACCEL = 202; -final static int RESP_SUCCESS_DATA_IMPEDANCE = 203; -final static int RESP_SUCCESS_DATA_SAMPLE = 204; -final static int RESP_WIFI_FOUND = 205; -final static int RESP_SUCCESS_CHANNEL_SETTING = 207; -final static int RESP_STATUS_CONNECTED = 300; -final static int RESP_STATUS_DISCONNECTED = 301; -final static int RESP_STATUS_SCANNING = 302; -final static int RESP_STATUS_NOT_SCANNING = 303; - -final static int LATENCY_5_MS = 5000; -final static int LATENCY_10_MS = 10000; -final static int LATENCY_20_MS = 20000; - -final static String TCP = "tcp"; -final static String UDP = "udp"; -final static String UDP_BURST = "udpBurst"; - -final static String WIFI_DYNAMIC = "dynamic"; -final static String WIFI_STATIC = "static"; - -void clientEvent(Client someClient) { - int p; - char newChar; - - newChar = hub.tcpClient.readChar(); - while(newChar != (char)-1) { - p = hub.tcpBufferPositon; - hub.tcpBuffer[p] = newChar; - hub.tcpBufferPositon++; - - if(p > 2) { - String posMatch = new String(hub.tcpBuffer, p - 1, 2); - if (posMatch.equals(TCP_STOP)) { - // println("MATCH"); - if (!hub.isHubRunning()) { - hub.setHubIsRunning(true); - println("Hub: clientEvent: handshake complete"); - } - // Get a string from the tcp buffer - String msg = new String(hub.tcpBuffer, 0, p); - // Send the new string message to be processed - - if (eegDataSource == DATASOURCE_GANGLION) { - hub.parseMessage(msg); - // Check to see if the ganglion ble list needs to be updated - if (hub.deviceListUpdated) { - hub.deviceListUpdated = false; - if (ganglion.isBLE()) { - controlPanel.bleBox.refreshBLEList(); - } else { - controlPanel.wifiBox.refreshWifiList(); - } - } - } else if (eegDataSource == DATASOURCE_CYTON) { - // Do stuff for cyton - hub.parseMessage(msg); - // Check to see if the ganglion ble list needs to be updated - if (hub.deviceListUpdated) { - hub.deviceListUpdated = false; - controlPanel.wifiBox.refreshWifiList(); - } - } - - // Reset the buffer position - hub.tcpBufferPositon = 0; - } - } - newChar = hub.tcpClient.readChar(); - } -} - -class Hub { - - public int curLatency = LATENCY_10_MS; - - public String[] deviceList = new String[0]; - public boolean deviceListUpdated = false; - - private int bleErrorCounter = 0; - private int prevSampleIndex = 0; - - private int requestedSampleRate = 0; - private boolean setSampleRate = false; - - private HubState state = HubState.NOCOM; - int prevState_millis = 0; // Used for calculating connect time out - - private int nEEGValuesPerPacket = 8; - private int nAuxValuesPerPacket = 3; - - private int tcpHubPort = 10996; - private String tcpHubIP = "127.0.0.1"; - - private String firmwareVersion = ""; - - private DataPacket_ADS1299 dataPacket; - - public Client tcpClient; - private boolean portIsOpen = false; - - public int numberOfDevices = 0; - private boolean hubRunning = false; - public char[] tcpBuffer = new char[4096]; - public int tcpBufferPositon = 0; - private String curProtocol = PROTOCOL_WIFI; - private String curInternetProtocol = TCP; - private String curWiFiStyle = WIFI_DYNAMIC; - - private boolean waitingForResponse = false; - private boolean searching = false; - private boolean checkingImpedance = false; - private boolean accelModeActive = false; - private boolean newAccelData = false; - public int[] accelArray = new int[NUM_ACCEL_DIMS]; - public int[] validAccelValues = {0, 0, 0}; - public int validLastMarker; - public int[] impedanceArray = new int[NCHAN_GANGLION + 1]; - - // Getters - public HubState get_state() { return state; } - public int getLatency() { return curLatency; } - public String getWifiInternetProtocol() { return curInternetProtocol; } - public String getWiFiStyle() { return curWiFiStyle; } - public boolean isPortOpen() { return portIsOpen; } - public boolean isHubRunning() { return hubRunning; } - public boolean isSearching() { return searching; } - public boolean isCheckingImpedance() { return checkingImpedance; } - public boolean isAccelModeActive() { return accelModeActive; } - public void setLatency(int latency) { - curLatency = latency; - println("Setting Latency to " + latency); - } - public void setWifiInternetProtocol(String internetProtocol) { - curInternetProtocol = internetProtocol; - println("Setting WiFi Internet Protocol to " + internetProtocol); - } - public void setWiFiStyle(String wifiStyle) { - curWiFiStyle = wifiStyle; - println("Setting WiFi style to " + wifiStyle); - } - - private PApplet mainApplet; - - //constructors - Hub() {}; //only use this if you simply want access to some of the constants - Hub(PApplet applet) { - mainApplet = applet; - - /* - // Able to start tcpClient connection? - if(!startTCPClient()) { - outputWarn("Failed to connect to OpenBCIHub background application. LIVE functionality will be disabled."); - println("InterfaceHub: Hub error"); - } - */ - } - - public void initDataPackets(int _nEEGValuesPerPacket, int _nAuxValuesPerPacket) { - nEEGValuesPerPacket = _nEEGValuesPerPacket; - nAuxValuesPerPacket = _nAuxValuesPerPacket; - // For storing data into - dataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels - for(int i = 0; i < nEEGValuesPerPacket; i++) { - dataPacket.values[i] = 0; - } - for(int i = 0; i < nAuxValuesPerPacket; i++){ - dataPacket.auxValues[i] = 0; - } - } - - /** - * @description Used to `try` and start the tcpClient - * @param applet {PApplet} - The main applet. - * @return {boolean} - True if able to start. - */ - public boolean startTCPClient() { - tcpClient = new Client(mainApplet, tcpHubIP, tcpHubPort); - return tcpClient.active(); - } - - public String getHubIP() { return tcpHubIP; } - public int getHubPort() { return tcpHubPort; } - - /** - * Sends a status message to the node process. - */ - public boolean getStatus() { - try { - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_STATUS); - writeJSON(json); - waitingForResponse = true; - return true; - } catch (NullPointerException E) { - // The tcp client is not initalized, try now - - return false; - } - } - - public void setHubIsRunning(boolean isRunning) { - hubRunning = isRunning; - } - - // Return true if the display needs to be updated for the BLE list - public void parseMessage(String data) { - JSONObject json = parseJSONObject(data); - if (json == null) { - println("JSONObject could not be parsed" + data); - } else { - String type = json.getString(TCP_JSON_KEY_TYPE); - if (type.equals(TCP_TYPE_ACCEL)) { - processAccel(json); - } else if (type.equals(TCP_TYPE_BOARD_TYPE)) { - processBoardType(json); - } else if (type.equals(TCP_TYPE_CHANNEL_SETTINGS)) { - processRegisterQuery(json); - } else if (type.equals(TCP_TYPE_COMMAND)) { - processCommand(json); - } else if (type.equals(TCP_TYPE_CONNECT)) { - processConnect(json); - } else if (type.equals(TCP_TYPE_DATA)) { - processData(json); - } else if (type.equals(TCP_TYPE_DISCONNECT)) { - processDisconnect(json); - } else if (type.equals(TCP_TYPE_ERROR)) { - int code = json.getInt(TCP_JSON_KEY_CODE); - String errorMessage = json.getString(TCP_JSON_KEY_MESSAGE); - println("Hub: parseMessage: error: " + errorMessage); - if (code == RESP_ERROR_COMMAND_NOT_RECOGNIZED) { - output("Hub in data folder outdated. Download a new hub for your OS at https://github.com/OpenBCI/OpenBCI_Hub/releases/latest"); - } - } else if (type.equals(TCP_TYPE_EXAMINE)) { - processExamine(json); - } else if (type.equals(TCP_TYPE_IMPEDANCE)) { - processImpedance(json); - } else if (type.equals(TCP_TYPE_LOG)) { - String logMessage = json.getString(TCP_JSON_KEY_MESSAGE); - println("Hub: Log: " + logMessage); - if (logMessage.startsWith("no daisy to attach")) cyton.daisyNotAttached = true; - } else if (type.equals(TCP_TYPE_PROTOCOL)) { - processProtocol(json); - } else if (type.equals(TCP_TYPE_SCAN)) { - processScan(json); - } else if (type.equals(TCP_TYPE_SD)) { - processSDCard(json); - } else if (type.equals(TCP_TYPE_STATUS)) { - processStatus(json); - } else if (type.equals(TCP_TYPE_WIFI)) { - processWifi(json); - } else { - println("Hub: parseMessage: default: " + data); - output("Hub in data folder outdated. Download a new hub for your OS at https://github.com/OpenBCI/OpenBCI_Hub/releases/latest"); - } - } - } - - private void writeJSON(JSONObject json) { - write(json.toString() + TCP_STOP); - } - - private void handleError(int code, String msg) { - output("Code " + code + " Error: " + msg); - } - - public void setBoardType(String boardType) { - println("Hub: setBoardType(): sending \'" + boardType + " -- " + millis()); - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_BOARD_TYPE); - json.setString(TCP_JSON_KEY_BOARD_TYPE, boardType); - writeJSON(json); - } - - private void processBoardType(JSONObject json) { - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_SUCCESS: - if (sdSetting > 0) { - println("Hub: processBoardType: success, starting SD card now -- " + millis()); - sdCardStart(sdSetting); - } else { - println("Hub: processBoardType: success -- " + millis()); - initAndShowGUI(); - } - break; - case RESP_ERROR_UNABLE_TO_SET_BOARD_TYPE: - default: - String msg = json.getString(TCP_JSON_KEY_MESSAGE); - killAndShowMsg("Unable to process board type. " + msg); - break; - } - } - - private void processConnect(JSONObject json) { - int code = json.getInt(TCP_JSON_KEY_CODE); - println("Hub: processConnect: made it -- " + millis() + " code: " + code); - switch (code) { - case RESP_SUCCESS: - case RESP_ERROR_ALREADY_CONNECTED: - firmwareVersion = json.getString(TCP_JSON_KEY_FIRMWARE); - changeState(HubState.SYNCWITHHARDWARE); - if (eegDataSource == DATASOURCE_CYTON) { - if (nchan == 8) { - setBoardType("cyton"); - } else { - setBoardType("daisy"); - } - } else { - println("Hub: parseMessage: connect: success! -- " + millis()); - initAndShowGUI(); - } - break; - case RESP_ERROR_UNABLE_TO_CONNECT: - println("Error in processConnect: RESP_ERROR_UNABLE_TO_CONNECT"); - String message = json.getString(TCP_JSON_KEY_MESSAGE); - if (message.equals("Error: Invalid sample rate")) { - if (eegDataSource == DATASOURCE_CYTON) { - killAndShowMsg("WiFi Shield is connected to a Ganglion. Please select LIVE (from Ganglion) instead of LIVE (from Cyton)"); - } else { - killAndShowMsg("WiFi Shield is connected to a Cyton. Please select LIVE (from Cyton) instead LIVE (from Cyton)"); - } - } else { - killAndShowMsg("Unable to Connect: " + message); - } - break; - case RESP_ERROR_WIFI_NEEDS_UPDATE: - println("Error in processConnect: RESP_ERROR_WIFI_NEEDS_UPDATE"); - killAndShowMsg("WiFi Shield Firmware is out of date. Learn to update: https://openbci.github.io/Documentation/docs/05ThirdParty/03-WiFiShield/WiFiProgam"); - break; - default: - println("Error in processConnect"); - message = json.getString(TCP_JSON_KEY_MESSAGE, "none"); - handleError(code, message); - break; - } - } - - private void processExamine(JSONObject json) { - // println(msg); - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_SUCCESS: - portIsOpen = true; - output("Connected to WiFi Shield named " + wifi_portName); - if (wcBox.isShowing) { - wcBox.updateMessage("Connected to WiFi Shield named " + wifi_portName); - } - break; - case RESP_ERROR_ALREADY_CONNECTED: - portIsOpen = true; - output("WiFi Shield is still connected to " + wifi_portName); - break; - case RESP_ERROR_UNABLE_TO_CONNECT: - output("No WiFi Shield found. Please visit https://openbci.github.io/Documentation/docs/01GettingStarted/01-Boards/WiFiGS"); - break; - default: - if (wcBox.isShowing) println("it is showing"); //controlPanel.hideWifiPopoutBox(); - String message = json.getString(TCP_JSON_KEY_MESSAGE, "none"); - handleError(code, message); - break; - } - } - - private void initAndShowGUI() { - changeState(HubState.NORMAL); - systemMode = SYSTEMMODE_POSTINIT; - controlPanel.close(); - topNav.controlPanelCollapser.setIsActive(false); - String firmwareString = " Cyton firmware "; - String settingsString = "Settings Loaded! "; - if (eegDataSource == DATASOURCE_CYTON) { - firmwareString += firmwareVersion; - if (settings.loadErrorCytonEvent == true) { - outputError("Connection Error: Failed to apply channel settings to Cyton."); - } else { - outputSuccess("The GUI is done initializing. " + settingsString + "Press \"Start Data Stream\" to start streaming! -- " + firmwareString); - } - } else if (eegDataSource == DATASOURCE_GANGLION) { - firmwareString = ganglion_portName; - outputSuccess("The GUI is done initializing. " + settingsString + "Press \"Start Data Stream\" to start streaming! -- " + firmwareString); - } else { - firmwareString = ""; - } - - - portIsOpen = true; - controlPanel.hideAllBoxes(); - } - - private void killAndShowMsg(String msg) { - println("Hub: killAndShowMsg: " + msg); - abandonInit = true; - initSystemButton.setString("START SESSION"); - controlPanel.open(); - portIsOpen = false; - haltSystem(); - } - - /** - * @description Sends a command to ganglion board - */ - public void sendCommand(char c) { - println("Hub: sendCommand(char): sending \'" + c + "\'"); - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_COMMAND); - json.setString(TCP_JSON_KEY_COMMAND, Character.toString(c)); - writeJSON(json); - } - - /** - * @description Sends a command to ganglion board - */ - public void sendCommand(String s) { - println("Hub: sendCommand(String): sending \'" + s + "\'"); - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_COMMAND); - json.setString(TCP_JSON_KEY_COMMAND, s); - writeJSON(json); - } - - public void processCommand(JSONObject json) { - String message = ""; - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_SUCCESS: - println("Hub: processCommand: success -- " + millis()); - break; - case RESP_ERROR_COMMAND_NOT_ABLE_TO_BE_SENT: - message = json.getString(TCP_JSON_KEY_MESSAGE, ""); - println("Hub: processCommand: ERROR_COMMAND_NOT_ABLE_TO_BE_SENT -- " + millis() + " " + message); - break; - case RESP_ERROR_PROTOCOL_NOT_STARTED: - message = json.getString(TCP_JSON_KEY_MESSAGE, ""); - println("Hub: processCommand: RESP_ERROR_PROTOCOL_NOT_STARTED -- " + millis() + " " + message); - break; - default: - break; - } - } - - public void processAccel(JSONObject json) { - int code = json.getInt(TCP_JSON_KEY_CODE); - if (code == RESP_SUCCESS_DATA_ACCEL) { - JSONArray accelDataCounts = json.getJSONArray(TCP_JSON_KEY_ACCEL_DATA_COUNTS); - for (int i = 0; i < NUM_ACCEL_DIMS; i++) { - accelArray[i] = accelDataCounts.getInt(i); - } - newAccelData = true; - if (accelArray[0] > 0 || accelArray[1] > 0 || accelArray[2] > 0) { - for (int i = 0; i < NUM_ACCEL_DIMS; i++) { - validAccelValues[i] = accelArray[i]; - } - } - } - } - - public int getMaxSampleIndex() { - if(curProtocol == PROTOCOL_BLE) { - return 200; - } - - return 255; - } - - public void processData(JSONObject json) { - try { - int code = json.getInt(TCP_JSON_KEY_CODE); - int stopByte = 0xC0; - numPacketsDroppedHub = 0; - if ((eegDataSource == DATASOURCE_GANGLION || eegDataSource == DATASOURCE_CYTON) && systemMode == 10 && isRunning) { - if (code == RESP_SUCCESS_DATA_SAMPLE) { - // set debugRandomlyDropPackets to true to simulate packet drops - if(debugRandomlyDropPackets && random(0, 1000) < 1) { - println("WARNING: Randomly dropping packet for debugging purposes"); - return; - } - // Sample number stuff - dataPacket.sampleIndex = json.getInt(TCP_JSON_KEY_SAMPLE_NUMBER); - boolean didWeRollOver = dataPacket.sampleIndex == 0 && prevSampleIndex == getMaxSampleIndex(); - - // if we rolled over, don't count as error - if (!didWeRollOver && (dataPacket.sampleIndex - prevSampleIndex) > 1) { - bleErrorCounter++; - - if(dataPacket.sampleIndex < prevSampleIndex){ //handle the situation in which the index jumps from 250s past 255, and back to 0 - numPacketsDroppedHub = (dataPacket.sampleIndex + getMaxSampleIndex()) - prevSampleIndex - 1; //calculate how many times the last received packet should be duplicated... - } else { - //calculate how many times the last received packet should be duplicated... - //Subtract 1 so this value is accurate (example 50->52, 52-50 = 2-1 = 1) - numPacketsDroppedHub = dataPacket.sampleIndex - prevSampleIndex - 1; - } - println("Hub: apparent sampleIndex jump from Serial data: " + prevSampleIndex + " to " + dataPacket.sampleIndex + ". Keeping packet. (" + bleErrorCounter + ")"); - println("numPacketsDropped = " + numPacketsDroppedHub); - } - prevSampleIndex = dataPacket.sampleIndex; - - // Channel data storage - JSONArray eegChannelDataCounts = json.getJSONArray(TCP_JSON_KEY_CHANNEL_DATA_COUNTS); - for (int i = 0; i < nEEGValuesPerPacket; i++) { - dataPacket.values[i] = eegChannelDataCounts.getInt(i); - } - if (newAccelData) { - newAccelData = false; - for (int i = 0; i < NUM_ACCEL_DIMS; i++) { - dataPacket.auxValues[i] = accelArray[i]; - dataPacket.rawAuxValues[i][0] = byte(accelArray[i]); - } - } else { - stopByte = json.getInt(TCP_JSON_KEY_STOP_BYTE); - if (stopByte == 0xC0) { - JSONArray accelValues = json.getJSONArray(TCP_JSON_KEY_ACCEL_DATA_COUNTS); - for (int i = 0; i < accelValues.size(); i++) { - accelArray[i] = accelValues.getInt(i); - dataPacket.auxValues[i] = accelArray[i]; - dataPacket.rawAuxValues[i][0] = byte(accelArray[i]); - dataPacket.rawAuxValues[i][1] = byte(accelArray[i] >> 8); - } - if (accelArray[0] > 0 || accelArray[1] > 0 || accelArray[2] > 0) { - // println(msg); - for (int i = 0; i < NUM_ACCEL_DIMS; i++) { - validAccelValues[i] = accelArray[i]; - } - } - } else { - JSONObject auxData = json.getJSONObject(TCP_JSON_KEY_AUX_DATA); - JSONArray auxDataValues; - if (nchan == NCHAN_CYTON_DAISY) { - JSONObject lowerAuxData = auxData.getJSONObject(TCP_JSON_KEY_LOWER); - auxDataValues = lowerAuxData.getJSONArray(TCP_JSON_KEY_DATA); - } else { - auxDataValues = auxData.getJSONArray(TCP_JSON_KEY_DATA); - } - int j = 0; - for (int i = 0; i < auxDataValues.size(); i+=2) { - int val1 = auxDataValues.getInt(i); - int val2 = auxDataValues.getInt(i+1); - - dataPacket.auxValues[j] = (val1 << 8) | val2; - validAccelValues[j] = (val1 << 8) | val2; - - dataPacket.rawAuxValues[j][0] = byte(val2); - dataPacket.rawAuxValues[j][1] = byte(val1 << 8); - j++; - } - } - } - getRawValues(dataPacket); - - // KILL SPIKES!!! - if(numPacketsDroppedHub > 0) { - println("Interpolating dropped packets..."); - - // the number of separations between the last received packet and the current packet. - // for example if we dropped 3 packets, we will have 4 divisions (marked with ---): - // 162, 163, 164 --- X --- X --- X --- 168, 169, 170... - int numSections = numPacketsDroppedHub + 1; - - DataPacket_ADS1299 current = dataPacket; - DataPacket_ADS1299 previous = dataPacketBuff[curDataPacketInd]; - - for (int i = 1; i <= numPacketsDroppedHub; i++){ - // increment current packet index - curDataPacketInd = (curDataPacketInd + 1) % dataPacketBuff.length; - - // this bias allows us to handle multiple dropped packets in a row - // by adjusting the lerp coefficient depending on which packet we are filling in - float bias = (float)i / (float)numSections; - DataPacket_ADS1299 interpolated = CreateInterpolatedPacket(previous, current, bias); - interpolated.copyTo(dataPacketBuff[curDataPacketInd]); - } - } - - curDataPacketInd = (curDataPacketInd + 1) % dataPacketBuff.length; // This is also used to let the rest of the code that it may be time to do something - copyDataPacketTo(dataPacketBuff[curDataPacketInd]); - - switch (outputDataSource) { - case OUTPUT_SOURCE_ODF: - if (eegDataSource == DATASOURCE_GANGLION) { - fileoutput_odf.writeRawData_dataPacket( - dataPacketBuff[curDataPacketInd], - ganglion.get_scale_fac_uVolts_per_count(), - ganglion.get_scale_fac_accel_G_per_count(), - stopByte, - json.getLong(TCP_JSON_KEY_TIMESTAMP) - ); - } else { - fileoutput_odf.writeRawData_dataPacket( - dataPacketBuff[curDataPacketInd], - cyton.get_scale_fac_uVolts_per_count(), - cyton.get_scale_fac_accel_G_per_count(), - stopByte, - json.getLong(TCP_JSON_KEY_TIMESTAMP) - ); - } - break; - case OUTPUT_SOURCE_BDF: - // curBDFDataPacketInd = curDataPacketInd; - // thread("writeRawData_dataPacket_bdf"); - fileoutput_bdf.writeRawData_dataPacket(dataPacketBuff[curDataPacketInd]); - break; - case OUTPUT_SOURCE_NONE: - default: - // Do nothing... - break; - } - } else { - bleErrorCounter++; - println("Hub: parseMessage: data: bad"); - } - } - } catch (Exception e) { - println("\n\n" + json + "\nHub: parseMessage: error: " + e); - } - } - - private void processDisconnect(JSONObject json) { - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_SUCCESS: - if (!waitingForResponse) { - if (eegDataSource == DATASOURCE_CYTON) { - killAndShowMsg("Dang! Lost connection to Cyton. Please move closer or get a new battery!"); - } else { - killAndShowMsg("Dang! Lost connection to Ganglion. Please move closer or get a new battery!"); - } - } else { - waitingForResponse = false; - } - break; - case RESP_ERROR_UNABLE_TO_DISCONNECT: - break; - } - portIsOpen = false; - } - - private void processImpedance(JSONObject json) { - String action = ""; - String message = ""; - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_ERROR_IMPEDANCE_COULD_NOT_START: - ganglion.overrideCheckingImpedance(false); - case RESP_ERROR_IMPEDANCE_COULD_NOT_STOP: - case RESP_ERROR_IMPEDANCE_FAILED_TO_SET_IMPEDANCE: - case RESP_ERROR_IMPEDANCE_FAILED_TO_PARSE: - message = json.getString(TCP_JSON_KEY_MESSAGE); - handleError(code, message); - break; - case RESP_SUCCESS_DATA_IMPEDANCE: - ganglion.processImpedance(json); - break; - case RESP_SUCCESS: - action = json.getString(TCP_JSON_KEY_ACTION); - output("Success: Impedance " + action + "."); - if (eegDataSource == DATASOURCE_GANGLION && action.equals("stop")) { - //Change the ganglion impedance button text when the user clicks start data stream - if (!w_ganglionImpedance.startStopCheck.getButtonText().equals("Start Impedance Check")) { - w_ganglionImpedance.startStopCheck.setString("Start Impedance Check"); - } - } - break; - default: - message = json.getString(TCP_JSON_KEY_MESSAGE); - handleError(code, message); - break; - } - } - - private void processProtocol(JSONObject json) { - String message, protocol; - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_SUCCESS: - protocol = json.getString(TCP_JSON_KEY_PROTOCOL); - output("Transfer Protocol set to " + protocol); - if (eegDataSource == DATASOURCE_GANGLION && ganglion.isBLE()) { - // hub.searchDeviceStart(); - outputInfo("BLE was powered up sucessfully, now searching for BLE devices."); - } - break; - case RESP_ERROR_PROTOCOL_BLE_START: - outputError("Failed to start Ganglion BLE Driver, please see https://openbci.github.io/Documentation/docs/01GettingStarted/01-Boards/GanglionGS"); - break; - default: - message = json.getString(TCP_JSON_KEY_MESSAGE); - verbosePrint("Hub: ProcessProtocol: Error Code " + code); - handleError(code, message); - break; - } - } - - private void processStatus(JSONObject json) { - int code = json.getInt(TCP_JSON_KEY_CODE); - if (waitingForResponse) { - waitingForResponse = false; - println("Node process is up!"); - } - if (code == RESP_ERROR_BAD_NOBLE_START) { - println("Hub: processStatus: Problem in the Hub"); - output("Problem starting Ganglion Hub. Please make sure compatible USB is configured, then restart this GUI."); - } else { - println("Hub: processStatus: Started Successfully"); - } - } - - private void processRegisterQuery(JSONObject json) { - String action = ""; - String message = ""; - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_ERROR_CHANNEL_SETTINGS: - killAndShowMsg("Failed to sync with Cyton, please power cycle your dongle and board."); - message = json.getString(TCP_JSON_KEY_MESSAGE); - println("RESP_ERROR_CHANNEL_SETTINGS general error: " + message); - break; - case RESP_ERROR_CHANNEL_SETTINGS_SYNC_IN_PROGRESS: - println("tried to sync channel settings but there was already one in progress"); - break; - case RESP_ERROR_CHANNEL_SETTINGS_FAILED_TO_SET_CHANNEL: - message = json.getString(TCP_JSON_KEY_MESSAGE); - println("an error was thrown trying to set the channels | error: " + message); - break; - case RESP_ERROR_CHANNEL_SETTINGS_FAILED_TO_PARSE: - message = json.getString(TCP_JSON_KEY_MESSAGE); - println("an error was thrown trying to call the function to set the channels | error: " + message); - break; - case RESP_SUCCESS: - // Sent when either a scan was stopped or started Successfully - action = json.getString(TCP_JSON_KEY_ACTION); - if (action.equals(TCP_ACTION_START)) { - println("Query registers for cyton channel settings"); - } else if (action.equals(TCP_ACTION_SET)) { - settings.checkForSuccessTS = json.getInt(TCP_JSON_KEY_CODE); - println("Success writing channel " + json.getInt(TCP_JSON_KEY_CHANNEL_NUMBER)); - - } - break; - case RESP_SUCCESS_CHANNEL_SETTING: - int channelNumber = json.getInt(TCP_JSON_KEY_CHANNEL_SET_CHANNEL_NUMBER); - // power down comes in as either 'true' or 'false', 'true' is a '1' and false is a '0' - channelSettingValues[channelNumber][0] = json.getBoolean(TCP_JSON_KEY_CHANNEL_SET_POWER_DOWN) ? '1' : '0'; - // gain comes in as an int, either 1, 2, 4, 6, 8, 12, 24 and must get converted to - // '0', '1', '2', '3', '4', '5', '6' respectively, of course. - channelSettingValues[channelNumber][1] = cyton.getCommandForGain(json.getInt(TCP_JSON_KEY_CHANNEL_SET_GAIN)); - // input type comes in as a string version and must get converted to char - channelSettingValues[channelNumber][2] = cyton.getCommandForInputType(json.getString(TCP_JSON_KEY_CHANNEL_SET_INPUT_TYPE)); - // bias is like power down - channelSettingValues[channelNumber][3] = json.getBoolean(TCP_JSON_KEY_CHANNEL_SET_BIAS) ? '1' : '0'; - // srb2 is like power down - channelSettingValues[channelNumber][4] = json.getBoolean(TCP_JSON_KEY_CHANNEL_SET_SRB2) ? '1' : '0'; - // srb1 is like power down - channelSettingValues[channelNumber][5] = json.getBoolean(TCP_JSON_KEY_CHANNEL_SET_SRB1) ? '1' : '0'; - break; - } - } - - private void processScan(JSONObject json) { - String action = ""; - String message = ""; - String name = ""; - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_GANGLION_FOUND: - case RESP_WIFI_FOUND: - // Sent every time a new ganglion device is found - name = json.getString(TCP_JSON_KEY_NAME, ""); - if (searchDeviceAdd(name)) { - deviceListUpdated = true; - } - break; - case RESP_ERROR_SCAN_ALREADY_SCANNING: - // Sent when a start send command is sent and the module is already - // scanning. - // handleError(code, list[2]); - searching = true; - break; - case RESP_SUCCESS: - // Sent when either a scan was stopped or started Successfully - action = json.getString(TCP_JSON_KEY_ACTION); - switch (action) { - case TCP_ACTION_START: - searching = true; - break; - case TCP_ACTION_STOP: - searching = false; - break; - } - break; - case RESP_ERROR_TIMEOUT_SCAN_STOPPED: - searching = false; - break; - case RESP_ERROR_SCAN_COULD_NOT_START: - // Sent when err on search start - message = json.getString(TCP_JSON_KEY_MESSAGE, ""); - verbosePrint("Hub: ProcessScan: Error Code: " + code); - if (code == RESP_ERROR_SCAN_COULD_NOT_START) { - outputError("Failed to start dongle. It may not be plugged in."); - } else { - handleError(code, message); - } - searching = false; - break; - case RESP_ERROR_SCAN_COULD_NOT_STOP: - // Send when err on search stop - message = json.getString(TCP_JSON_KEY_MESSAGE, ""); - handleError(code, message); - searching = false; - break; - case RESP_STATUS_SCANNING: - // Sent when after status action sent to node and module is searching - searching = true; - break; - case RESP_STATUS_NOT_SCANNING: - // Sent when after status action sent to node and module is NOT searching - searching = false; - break; - case RESP_ERROR_SCAN_NO_SCAN_TO_STOP: - // Sent when a 'stop' action is sent to node and there is no scan to stop. - // handleError(code, list[2]); - searching = false; - break; - case RESP_ERROR_UNKNOWN: - default: - message = json.getString(TCP_JSON_KEY_MESSAGE, ""); - handleError(code, message); - break; - } - } - - public void sdCardStart(int sdSetting) { - String sdSettingStr = cyton.getSDSettingForSetting(sdSetting); - println("Hub: sdCardStart(): sending \'" + sdSettingStr + "\' with value " + sdSetting); - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, TCP_ACTION_START); - json.setString(TCP_JSON_KEY_COMMAND, sdSettingStr); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_SD); - writeJSON(json); - } - - private void processSDCard(JSONObject json) { - String action, message; - int code = json.getInt(TCP_JSON_KEY_CODE); - action = json.getString(TCP_JSON_KEY_ACTION); - - switch(code) { - case RESP_SUCCESS: - // Sent when either a scan was stopped or started Successfully - switch (action) { - case TCP_ACTION_START: - println("sd card setting set so now attempting to sync channel settings"); - // cyton.syncChannelSettings(); - initAndShowGUI(); - break; - case TCP_ACTION_STOP: - message = json.getString(TCP_JSON_KEY_MESSAGE); - println("ProcessSDcard::Success:Stop: " + message); - break; - } - break; - case RESP_ERROR_UNKNOWN: - switch (action) { - case TCP_ACTION_START: - message = json.getString(TCP_JSON_KEY_MESSAGE); - killAndShowMsg("ProcessSDCard: " + message); - break; - case TCP_ACTION_STOP: - message = json.getString(TCP_JSON_KEY_MESSAGE); - println("ProcessSDcard::Unknown:Stop: " + message); - break; - } - break; - default: - message = json.getString(TCP_JSON_KEY_MESSAGE); - handleError(code, message); - break; - } - } - - void writeRawData_dataPacket_bdf() { - fileoutput_bdf.writeRawData_dataPacket(dataPacketBuff[curBDFDataPacketInd]); - } - - public int copyDataPacketTo(DataPacket_ADS1299 target) { - return dataPacket.copyTo(target); - } - - public String getFirmwareVersion() { - return firmwareVersion; - } - - private void getRawValues(DataPacket_ADS1299 packet) { - for (int i=0; i < nchan; i++) { - int val = packet.values[i]; - //println(binary(val, 24)); - byte rawValue[] = new byte[3]; - // Breakdown values into - rawValue[2] = byte(val & 0xFF); - //println("rawValue[2] " + binary(rawValue[2], 8)); - rawValue[1] = byte((val & (0xFF << 8)) >> 8); - //println("rawValue[1] " + binary(rawValue[1], 8)); - rawValue[0] = byte((val & (0xFF << 16)) >> 16); - //println("rawValue[0] " + binary(rawValue[0], 8)); - // Store to the target raw values - packet.rawValues[i] = rawValue; - } - } - - public boolean isSuccessCode(int c) { - return c == RESP_SUCCESS; - } - - public void updateSyncState(int sdSetting) { - //has it been 3000 milliseconds since we initiated the serial port? We want to make sure we wait for the OpenBCI board to finish its setup() - if ( (millis() - prevState_millis > COM_INIT_MSEC) && (prevState_millis != 0) && (state == HubState.COMINIT) ) { - state = HubState.SYNCWITHHARDWARE; - println("InterfaceHub: systemUpdate: [0] Sending 'v' to OpenBCI to reset hardware in case of 32bit board..."); - } - } - - public void closePort() { - switch (curProtocol) { - case PROTOCOL_BLE: - disconnectBLE(); - break; - case PROTOCOL_WIFI: - disconnectWifi(); - break; - case PROTOCOL_SERIAL: - disconnectSerial(); - break; - default: - break; - } - changeState(HubState.NOCOM); - } - - // CONNECTION - public void connectBLE(String id) { - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_NAME, id); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_CONNECT); - writeJSON(json); - verbosePrint("OpenBCI_GUI: hub : Sent connect to Hub - Id: " + id); - - } - public void disconnectBLE() { - waitingForResponse = true; - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_PROTOCOL, PROTOCOL_BLE); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_DISCONNECT); - writeJSON(json); - } - - public void connectWifi(String id) { - JSONObject json = new JSONObject(); - json.setInt(TCP_JSON_KEY_LATENCY, curLatency); - if (curInternetProtocol == UDP_BURST) { - json.setString(TCP_JSON_KEY_PROTOCOL, UDP); - json.setBoolean(TCP_JSON_KEY_BURST_MODE, true); - } else { - json.setString(TCP_JSON_KEY_PROTOCOL, curInternetProtocol); - json.setBoolean(TCP_JSON_KEY_BURST_MODE, false); - } - json.setInt(TCP_JSON_KEY_SAMPLE_RATE, requestedSampleRate); - json.setString(TCP_JSON_KEY_NAME, id); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_CONNECT); - writeJSON(json); - println("OpenBCI_GUI: hub : Sent connect to Hub - Id: " + id + " SampleRate: " + requestedSampleRate + "Hz Latency: " + curLatency + "ms"); - } - - public void examineWifi(String id) { - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_NAME, id); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_EXAMINE); - writeJSON(json); - } - - public int disconnectWifi() { - waitingForResponse = true; - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_PROTOCOL, PROTOCOL_WIFI); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_DISCONNECT); - writeJSON(json); - return 0; - } - - public void connectSerial(String id) { - waitingForResponse = true; - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_PROTOCOL, PROTOCOL_SERIAL); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_CONNECT); - json.setString(TCP_JSON_KEY_NAME, id); - writeJSON(json); - verbosePrint("OpenBCI_GUI: hub : Sent connect to Hub - Id: " + id); - delay(1000); - - } - public int disconnectSerial() { - println("Disconnecting serial..."); - waitingForResponse = true; - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_PROTOCOL, PROTOCOL_SERIAL); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_DISCONNECT); - writeJSON(json); - return 0; - } - - public void setProtocol(String _protocol) { - curProtocol = _protocol; - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, TCP_ACTION_START); - json.setString(TCP_JSON_KEY_PROTOCOL, curProtocol); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_PROTOCOL); - writeJSON(json); - } - - public String getProtocol() { - return curProtocol; - } - - public int getSampleRate() { - return requestedSampleRate; - } - - public void setSampleRate(int _sampleRate) { - requestedSampleRate = _sampleRate; - setSampleRate = true; - println("\n\nsample rate set to: " + _sampleRate); - } - - public void getWifiInfo(String info) { - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, info); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_WIFI); - writeJSON(json); - } - - private void processWifi(JSONObject json) { - String action = ""; - String message = ""; - int code = json.getInt(TCP_JSON_KEY_CODE); - switch (code) { - case RESP_ERROR_WIFI_ACTION_NOT_RECOGNIZED: - output("Sent an action to hub for wifi info but the command was unrecognized"); - break; - case RESP_ERROR_WIFI_NOT_CONNECTED: - output("Tried to get wifi info but no WiFi Shield was connected."); - break; - case RESP_ERROR_CHANNEL_SETTINGS_FAILED_TO_SET_CHANNEL: - message = json.getString(TCP_JSON_KEY_MESSAGE); - println("an error was thrown trying to set the channels | error: " + message); - break; - case RESP_ERROR_CHANNEL_SETTINGS_FAILED_TO_PARSE: - message = json.getString(TCP_JSON_KEY_MESSAGE); - println("an error was thrown trying to call the function to set the channels | error: " + message); - break; - case RESP_SUCCESS: - // Sent when either a scan was stopped or started Successfully - if (wcBox.isShowing) { - String msgForWcBox = json.getString(TCP_JSON_KEY_MESSAGE); - String command = json.getString(TCP_JSON_KEY_COMMAND); - switch (command) { - case TCP_WIFI_GET_TYPE_OF_ATTACHED_BOARD: - switch(message) { - case "none": - msgForWcBox = "No OpenBCI Board attached to WiFi Shield"; - break; - case "ganglion": - msgForWcBox = "4-channel Ganglion attached to WiFi Shield"; - break; - case "cyton": - msgForWcBox = "8-channel Cyton attached to WiFi Shield"; - break; - case "daisy": - msgForWcBox = "16-channel Cyton with Daisy attached to WiFi Shield"; - break; - } - break; - case TCP_WIFI_ERASE_CREDENTIALS: - output("WiFi credentials have been erased and WiFi Shield is in hotspot mode. If erase fails, remove WiFi Shield from OpenBCI Board."); - msgForWcBox = ""; - controlPanel.hideWifiPopoutBox(); - wifi_portName = "N/A"; - clearDeviceList(); - controlPanel.wifiBox.refreshWifiList(); - break; - } - println("Success for wifi " + command + ": " + msgForWcBox); - wcBox.updateMessage(msgForWcBox); - } - break; - } - } - - /** - * @description Write to TCP server - * @params out {String} - The string message to write to the server. - * @returns {boolean} - True if able to write, false otherwise. - */ - public boolean write(String out) { - try { - // println("out " + out); - tcpClient.write(out); - return true; - } catch (Exception e) { - if (isWindows()) { - killAndShowMsg("Please start OpenBCIHub before launching this application."); - } else { - killAndShowMsg("Hub has crashed, please restart your application."); - } - println("Error: Attempted to TCP write with no server connection initialized"); - return false; - } - } - public boolean write(char val) { - return write(String.valueOf(val)); - } - - public int changeState(HubState newState) { - state = newState; - prevState_millis = millis(); - return 0; - } - - public void clearDeviceList() { - deviceList = null; - numberOfDevices = 0; - } - - public void searchDeviceStart() { - clearDeviceList(); - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, TCP_ACTION_START); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_SCAN); - writeJSON(json); - } - - public void searchDeviceStop() { - JSONObject json = new JSONObject(); - json.setString(TCP_JSON_KEY_ACTION, TCP_ACTION_STOP); - json.setString(TCP_JSON_KEY_TYPE, TCP_TYPE_SCAN); - writeJSON(json); - } - - public boolean searchDeviceAdd(String localName) { - if (numberOfDevices == 0) { - numberOfDevices++; - deviceList = new String[numberOfDevices]; - deviceList[0] = localName; - return true; - } else { - boolean willAddToDeviceList = true; - for (int i = 0; i < numberOfDevices; i++) { - if (localName.equals(deviceList[i])) { - willAddToDeviceList = false; - break; - } - } - if (willAddToDeviceList) { - numberOfDevices++; - String[] tempList = new String[numberOfDevices]; - arrayCopy(deviceList, tempList); - tempList[numberOfDevices - 1] = localName; - deviceList = tempList; - return true; - } - } - return false; - } - -}; - -class CheckHubInit extends TimerTask { - public void run() { - //Every hubTimerInterval seconds until hubTimerLimit is reached - //try to open a new socket. If successful, close the socket and try to startTCPClient. - try { - Socket socket = new Socket(hub.getHubIP(), hub.getHubPort()); - if (socket != null) { - socket.close(); - socket = null; - if (hub.startTCPClient()) { - if (hubTimerCounter > 0) { - outputSuccess("The GUI is connected to the Hub!"); - } else { - println("Hub: CheckHubInit: The GUI is connected to the Hub!"); - } - hub.setHubIsRunning(true); - this.cancel(); - } else { - outputError("Hub: CheckHubInit: Unable to startTCPClient even though a socket was opened..."); - } - } - } catch (IOException e) { - outputWarn("Unable to establish link with the OpenBCI Hub, trying again..."); - } - - hubTimerCounter++; - } -} diff --git a/OpenBCI_GUI/InterfaceSerial.pde b/OpenBCI_GUI/InterfaceSerial.pde index b7712ee01..81a38285c 100644 --- a/OpenBCI_GUI/InterfaceSerial.pde +++ b/OpenBCI_GUI/InterfaceSerial.pde @@ -37,21 +37,6 @@ long timeSinceStopRunning = 1000; boolean werePacketsDroppedSerial = false; int numPacketsDroppedSerial = 0; - -//everything below is now deprecated... -// final String[] command_activate_leadoffP_channel = {"!", "@", "#", "$", "%", "^", "&", "*"}; //shift + 1-8 -// final String[] command_deactivate_leadoffP_channel = {"Q", "W", "E", "R", "T", "Y", "U", "I"}; //letters (plus shift) right below 1-8 -// final String[] command_activate_leadoffN_channel = {"A", "S", "D", "F", "G", "H", "J", "K"}; //letters (plus shift) below the letters below 1-8 -// final String[] command_deactivate_leadoffN_channel = {"Z", "X", "C", "V", "B", "N", "M", "<"}; //letters (plus shift) below the letters below the letters below 1-8 -// final String command_biasAuto = "`"; -// final String command_biasFixed = "~"; - -// ArrayList defaultChannelSettings; - -//here is the routine that listens to the serial port. -//if any data is waiting, get it, parse it, and stuff it into our vector of -//pre-allocated dataPacketBuff - //------------------------------------------------------------------------ // Global Functions //------------------------------------------------------------------------ @@ -60,7 +45,6 @@ void serialEvent(Serial port){ //check to see which serial port it is if (iSerial.isOpenBCISerial(port)) { - // boolean echoBytes = !cyton.isStateNormal(); boolean echoBytes; if (iSerial.isStateNormal() != true) { // || printingRegisters == true){ @@ -69,47 +53,12 @@ void serialEvent(Serial port){ echoBytes = false; } iSerial.read(echoBytes); - openBCI_byteCount++; if (iSerial.get_isNewDataPacketAvailable()) { println("woo got a new packet"); //copy packet into buffer of data packets - curDataPacketInd = (curDataPacketInd+1) % dataPacketBuff.length; //this is also used to let the rest of the code that it may be time to do something - cyton.copyDataPacketTo(dataPacketBuff[curDataPacketInd]); iSerial.set_isNewDataPacketAvailable(false); //resets isNewDataPacketAvailable to false - // KILL SPIKES!!! - if(werePacketsDroppedSerial){ - for(int i = numPacketsDroppedSerial; i > 0; i--){ - int tempDataPacketInd = curDataPacketInd - i; // - if(tempDataPacketInd >= 0 && tempDataPacketInd < dataPacketBuff.length){ - cyton.copyDataPacketTo(dataPacketBuff[tempDataPacketInd]); - } else { - cyton.copyDataPacketTo(dataPacketBuff[tempDataPacketInd+255]); - } - //put the last stored packet in # of packets dropped after that packet - } - - //reset werePacketsDroppedSerial & numPacketsDroppedSerial - werePacketsDroppedSerial = false; - numPacketsDroppedSerial = 0; - } - - switch (outputDataSource) { - case OUTPUT_SOURCE_ODF: - fileoutput_odf.writeRawData_dataPacket(dataPacketBuff[curDataPacketInd], cyton.get_scale_fac_uVolts_per_count(), cyton.get_scale_fac_accel_G_per_count(), byte(0xC0), (new Date()).getTime()); - break; - case OUTPUT_SOURCE_BDF: - curBDFDataPacketInd = curDataPacketInd; - thread("writeRawData_dataPacket_bdf"); - // fileoutput_bdf.writeRawData_dataPacket(dataPacketBuff[curDataPacketInd]); - break; - case OUTPUT_SOURCE_NONE: - default: - // Do nothing... - break; - } - newPacketCounter++; } } else { @@ -201,9 +150,7 @@ class InterfaceSerial { private int nEEGValuesPerPacket = 8; //defined by the data format sent by cyton boards private int nAuxValuesPerPacket = 3; //defined by the data format sent by cyton boards - private DataPacket_ADS1299 rawReceivedDataPacket; - private DataPacket_ADS1299 missedDataPacket; - private DataPacket_ADS1299 dataPacket; + public int [] validAuxValues = {0, 0, 0}; public boolean[] freshAuxValuesAvailable = {false, false, false}; public boolean freshAuxValues = false; @@ -212,7 +159,6 @@ class InterfaceSerial { private int nAuxValues; private boolean isNewDataPacketAvailable = false; private OutputStream output; //for debugging WEA 2014-01-26 - private int prevSampleIndex = 0; private int serialErrorCounter = 0; private final float fs_Hz = 250.0f; //sample rate used by OpenBCI board...set by its Arduino code @@ -267,34 +213,6 @@ class InterfaceSerial { } dataMode = prefered_datamode; - - initDataPackets(nEEGValuesPerOpenBCI, nAuxValuesPerOpenBCI); - - } - - public void initDataPackets(int numEEG, int numAux) { - nEEGValuesPerPacket = numEEG; - nAuxValuesPerPacket = numAux; - //allocate space for data packet - rawReceivedDataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels - missedDataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this should always be 8 channels - dataPacket = new DataPacket_ADS1299(nEEGValuesPerPacket, nAuxValuesPerPacket); //this could be 8 or 16 channels - - for (int i = 0; i < nEEGValuesPerPacket; i++) { - rawReceivedDataPacket.values[i] = 0; - //prevDataPacket.values[i] = 0; - } - for (int i=0; i < nEEGValuesPerPacket; i++) { - // println("i = " + i); - dataPacket.values[i] = 0; - missedDataPacket.values[i] = 0; - } - for (int i = 0; i < nAuxValuesPerPacket; i++) { - rawReceivedDataPacket.auxValues[i] = 0; - dataPacket.auxValues[i] = 0; - missedDataPacket.auxValues[i] = 0; - //prevDataPacket.auxValues[i] = 0; - } } // //manage the serial port @@ -318,7 +236,7 @@ class InterfaceSerial { } else { println("RunttimeException: " + e); output("Error connecting to selected Serial/COM port. Make sure your board is powered up and your dongle is plugged in."); - abandonInit = true; //global variable in OpenBCI_GUI.pde + //abandonInit = true; //global variable in OpenBCI_GUI.pde } return 0; } @@ -338,12 +256,9 @@ class InterfaceSerial { public int closeSDandSerialPort() { int returnVal=0; - cyton.closeSDFile(); - readyToSend = false; returnVal = closeSerialPort(); prevState_millis = 0; //reset Serial state clock to use as a conditional for timing at the beginnign of systemUpdate() - cyton.hardwareSyncStep = 0; //reset Hardware Sync step to be ready to go again... return returnVal; } @@ -367,9 +282,6 @@ class InterfaceSerial { state = STATE_SYNCWITHHARDWARE; timeOfLastCommand = millis(); serial_openBCI.clear(); - cyton.potentialFailureMessage = ""; - cyton.defaultChannelSettings = ""; //clear channel setting string to be reset upon a new Init System - cyton.daisyOrNot = ""; //clear daisyOrNot string to be reset upon a new Init System println("InterfaceSerial: systemUpdate: [0] Sending 'v' to OpenBCI to reset hardware in case of 32bit board..."); serial_openBCI.write('v'); } @@ -379,8 +291,6 @@ class InterfaceSerial { if (millis() - timeOfLastCommand > 200 && readyToSend == true) { println("sdSetting: " + sdSetting); timeOfLastCommand = millis(); - cyton.hardwareSyncStep++; - cyton.syncWithHardware(sdSetting); } } } @@ -452,57 +362,14 @@ class InterfaceSerial { prev3chars[1] = prev3chars[2]; prev3chars[2] = inASCII; - if (cyton.hardwareSyncStep == 0 && inASCII != '$') { - cyton.potentialFailureMessage+=inASCII; - } - - if (cyton.hardwareSyncStep == 1 && inASCII != '$') { - cyton.daisyOrNot+=inASCII; - //if hardware returns 8 because daisy is not attached, switch the GUI mode back to 8 channels - // if(nchan == 16 && char(daisyOrNot.substring(daisyOrNot.length() - 1)) == '8'){ - if (nchan == 16 && cyton.daisyOrNot.charAt(cyton.daisyOrNot.length() - 1) == '8') { - // verbosePrint(" received from OpenBCI... Switching to nchan = 8 bc daisy is not present..."); - verbosePrint(" received from OpenBCI... Abandoning hardware initiation."); - abandonInit = true; - // haltSystem(); - - // updateToNChan(8); - // - // //initialize the FFT objects - // for (int Ichan=0; Ichan < nchan; Ichan++) { - // verbosePrint("Init FFT Buff – "+Ichan); - // fftBuff[Ichan] = new FFT(Nfft, getSampleRateSafe()); - // } //make the FFT objects - // - // initializeFFTObjects(fftBuff, dataBuffY_uV, Nfft, getSampleRateSafe()); - // setupWidgetManager(); - } - } - - if (cyton.hardwareSyncStep == 3 && inASCII != '$') { //if we're retrieving channel settings from OpenBCI - cyton.defaultChannelSettings+=inASCII; - } - //if the last three chars are $$$, it means we are moving on to the next stage of initialization if (prev3chars[0] == EOT[0] && prev3chars[1] == EOT[1] && prev3chars[2] == EOT[2]) { verbosePrint(" > EOT detected..."); // Added for V2 system down rejection line - if (cyton.hardwareSyncStep == 0) { - // Failure: Communications timeout - Device failed to poll Host$$$ - if (cyton.potentialFailureMessage.equals(failureMessage)) { - closeLogFile(); - return 0; - } - } + // hardwareSyncStep++; prev3chars[2] = '#'; - if (cyton.hardwareSyncStep == 3) { - println("InterfaceSerial: read(): x"); - println("InterfaceSerial: defaultChanSettings: " + cyton.defaultChannelSettings); - println("InterfaceSerial: read(): y"); - w_timeSeries.hsc.loadDefaultChannelSettings(); - println("InterfaceSerial: read(): z"); - } + readyToSend = true; // println(hardwareSyncStep); } @@ -562,29 +429,7 @@ class InterfaceSerial { //check the packet counter // println("case 1"); byte inByte = actbyte; - rawReceivedDataPacket.sampleIndex = int(inByte); //changed by JAM - if ((rawReceivedDataPacket.sampleIndex-prevSampleIndex) != 1) { - if (rawReceivedDataPacket.sampleIndex != 0) { // if we rolled over, don't count as error - serialErrorCounter++; - werePacketsDroppedSerial = true; //set this true to activate packet duplication in serialEvent - - if(rawReceivedDataPacket.sampleIndex < prevSampleIndex){ //handle the situation in which the index jumps from 250s past 255, and back to 0 - numPacketsDroppedSerial = (rawReceivedDataPacket.sampleIndex+255) - prevSampleIndex; //calculate how many times the last received packet should be duplicated... - } else { - numPacketsDroppedSerial = rawReceivedDataPacket.sampleIndex - prevSampleIndex; //calculate how many times the last received packet should be duplicated... - } - - println("InterfaceSerial: apparent sampleIndex jump from Serial data: " + prevSampleIndex + " to " + rawReceivedDataPacket.sampleIndex + ". Keeping packet. (" + serialErrorCounter + ")"); - if (outputDataSource == OUTPUT_SOURCE_BDF) { - int fakePacketsToWrite = (rawReceivedDataPacket.sampleIndex - prevSampleIndex) - 1; - for (int i = 0; i < fakePacketsToWrite; i++) { - fileoutput_bdf.writeRawData_dataPacket(missedDataPacket); - } - println("InterfaceSerial: because BDF, wrote " + fakePacketsToWrite + " empty data packet(s)"); - } - } - } - prevSampleIndex = rawReceivedDataPacket.sampleIndex; + localByteCounter=0;//prepare for next usage of localByteCounter localChannelCounter=0; //prepare for next usage of localChannelCounter PACKET_readstate++; @@ -595,8 +440,6 @@ class InterfaceSerial { localAdsByteBuffer[localByteCounter] = actbyte; localByteCounter++; if (localByteCounter==3) { - rawReceivedDataPacket.values[localChannelCounter] = interpret24bitAsInt32(localAdsByteBuffer); - arrayCopy(localAdsByteBuffer, rawReceivedDataPacket.rawValues[localChannelCounter]); localChannelCounter++; if (localChannelCounter==8) { //nDataValuesInPacket) { // all ADS channels arrived ! @@ -617,26 +460,7 @@ class InterfaceSerial { // println("case 3"); localAccelByteBuffer[localByteCounter] = actbyte; localByteCounter++; - if (localByteCounter==2) { - rawReceivedDataPacket.auxValues[localChannelCounter] = interpret16bitAsInt32(localAccelByteBuffer); - arrayCopy(localAccelByteBuffer, rawReceivedDataPacket.rawAuxValues[localChannelCounter]); - if (rawReceivedDataPacket.auxValues[localChannelCounter] != 0) { - validAuxValues[localChannelCounter] = rawReceivedDataPacket.auxValues[localChannelCounter]; - freshAuxValuesAvailable[localChannelCounter] = true; - freshAuxValues = true; - } else freshAuxValues = false; - localChannelCounter++; - if (localChannelCounter==nAuxValues) { //number of accelerometer axis) { - // all Accelerometer channels arrived ! - // println("InterfaceSerial: interpretBinaryStream: Accel Data: " + rawReceivedDataPacket.auxValues[0] + ", " + rawReceivedDataPacket.auxValues[1] + ", " + rawReceivedDataPacket.auxValues[2]); - PACKET_readstate++; - localByteCounter = 0; - //isNewDataPacketAvailable = true; //tell the rest of the code that the data packet is complete - } else { - //prepare for next data channel - localByteCounter=0; //prepare for next usage of localByteCounter - } - } + break; case 4: //look for end byte @@ -658,9 +482,7 @@ class InterfaceSerial { PACKET_readstate=0; // look for next packet } - if (flag_copyRawDataToFullData) { - copyRawDataToFullData(); - } + } // end of interpretBinaryStream @@ -674,61 +496,5 @@ class InterfaceSerial { } } - private int interpret24bitAsInt32(byte[] byteArray) { - //little endian - int newInt = ( - ((0xFF & byteArray[0]) << 16) | - ((0xFF & byteArray[1]) << 8) | - (0xFF & byteArray[2]) - ); - if ((newInt & 0x00800000) > 0) { - newInt |= 0xFF000000; - } else { - newInt &= 0x00FFFFFF; - } - return newInt; - } - - private int interpret16bitAsInt32(byte[] byteArray) { - int newInt = ( - ((0xFF & byteArray[0]) << 8) | - (0xFF & byteArray[1]) - ); - if ((newInt & 0x00008000) > 0) { - newInt |= 0xFFFF0000; - } else { - newInt &= 0x0000FFFF; - } - return newInt; - } - - - private int copyRawDataToFullData() { - //Prior to the 16-chan OpenBCI, we did NOT have rawReceivedDataPacket along with dataPacket...we just had dataPacket. - //With the 16-chan OpenBCI, where the first 8 channels are sent and then the second 8 channels are sent, we introduced - //this extra structure so that we could alternate between them. - // - //This function here decides how to join the latest data (rawReceivedDataPacket) into the full dataPacket - - if (dataPacket.values.length < 2*rawReceivedDataPacket.values.length) { - //this is an 8 channel board, so simply copy the data - return rawReceivedDataPacket.copyTo(dataPacket); - } else { - //this is 16-channels, so copy the raw data into the correct channels of the new data - int offsetInd_values = 0; //this is correct assuming we just recevied a "board" packet (ie, channels 1-8) - int offsetInd_aux = 0; //this is correct assuming we just recevied a "board" packet (ie, channels 1-8) - if (rawReceivedDataPacket.sampleIndex % 2 == 0) { // even data packets are from the daisy board - offsetInd_values = rawReceivedDataPacket.values.length; //start copying to the 8th slot - //offsetInd_aux = rawReceivedDataPacket.auxValues.length; //start copying to the 3rd slot - offsetInd_aux = 0; - } - return rawReceivedDataPacket.copyTo(dataPacket, offsetInd_values, offsetInd_aux); - } - } - - public int copyDataPacketTo(DataPacket_ADS1299 target) { - isNewDataPacketAvailable = false; - return dataPacket.copyTo(target); - } }; diff --git a/OpenBCI_GUI/OpenBCI_GUI.pde b/OpenBCI_GUI/OpenBCI_GUI.pde index d2d63f1ab..a7874b5c8 100644 --- a/OpenBCI_GUI/OpenBCI_GUI.pde +++ b/OpenBCI_GUI/OpenBCI_GUI.pde @@ -23,8 +23,6 @@ import ddf.minim.ugens.*; // To make sound. Following minim example "frequencyM import java.lang.Math; //for exp, log, sqrt...they seem better than Processing's built-in import processing.core.PApplet; import java.util.*; //for Array.copyOfRange() -import java.util.Map.Entry; -import java.util.Map; import processing.serial.*; //for serial communication to Arduino/OpenBCI import java.awt.event.*; //to allow for event listener on screen resize import processing.net.*; // For TCP networking @@ -39,7 +37,8 @@ import java.awt.MouseInfo; import java.lang.Process; import java.text.DateFormat; //Used in DataLogging.pde import java.text.SimpleDateFormat; -import java.time.LocalTime; +import java.time.LocalDateTime; +import java.time.Instant; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; // import java.net.InetAddress; // Used for ping, however not working right now. @@ -52,25 +51,24 @@ import hypermedia.net.*; //for UDP import java.nio.ByteBuffer; //for UDP import edu.ucsd.sccn.LSL; //for LSL //These are used by LSL -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.Platform; -import com.sun.jna.Pointer; +//import com.sun.jna.Library; +//import com.sun.jna.Native; +//import com.sun.jna.Platform; +//import com.sun.jna.Pointer; +import com.fazecast.jSerialComm.*; //Helps distinguish serial ports on Windows //------------------------------------------------------------------------ // Global Variables & Instances //------------------------------------------------------------------------ //Used to check GUI version in TopNav.pde and displayed on the splash screen on startup -String localGUIVersionString = "v4.2.0"; -String localGUIVersionDate = "January 2020"; +String localGUIVersionString = "v5.0.0"; +String localGUIVersionDate = "August 2020"; String guiLatestReleaseLocation = "https://github.com/OpenBCI/OpenBCI_GUI/releases/latest"; Boolean guiVersionCheckHasOccured = false; -DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); //used to switch between application states final int SYSTEMMODE_INTROANIMATION = -10; final int SYSTEMMODE_PREINIT = 0; -final int SYSTEMMODE_MIDINIT = 5; final int SYSTEMMODE_POSTINIT = 10; int systemMode = SYSTEMMODE_INTROANIMATION; /* Modes: -10 = intro sequence; 0 = system stopped/control panel setings; 10 = gui; 20 = help guide */ @@ -78,6 +76,7 @@ boolean midInit = false; boolean midInitCheck2 = false; boolean abandonInit = false; boolean systemHasHalted = false; +boolean reinitRequested = false; final int NCHAN_CYTON = 8; final int NCHAN_CYTON_DAISY = 16; @@ -92,30 +91,33 @@ final int DATASOURCE_CYTON = 0; // new default, data from serial with Accel data final int DATASOURCE_GANGLION = 1; //looking for signal from OpenBCI board via Serial/COM port, no Aux data final int DATASOURCE_PLAYBACKFILE = 2; //playback from a pre-recorded text file final int DATASOURCE_SYNTHETIC = 3; //Synthetically generated data +final int DATASOURCE_NOVAXR = 4; public int eegDataSource = -1; //default to none of the options +final static int NUM_ACCEL_DIMS = 3; -final int INTERFACE_NONE = -1; // Used to indicate no choice made yet on interface -final int INTERFACE_SERIAL = 0; // Used only by cyton -final int INTERFACE_HUB_BLE = 1; // used only by ganglion -final int INTERFACE_HUB_WIFI = 2; // used by both cyton and ganglion -final int INTERFACE_HUB_BLED112 = 3; // used only by ganglion with bled dongle +enum BoardProtocol { + NONE, + SERIAL, + BLE, + WIFI, + BLED112 +} +public BoardProtocol selectedProtocol = BoardProtocol.NONE; boolean showStartupError = false; String startupErrorMessage = ""; //here are variables that are used if loading input data from a CSV text file...double slash ("\\") is necessary to make a single slash String playbackData_fname = "N/A"; //only used if loading input data from a file -// String playbackData_fname; //leave blank to cause an "Open File" dialog box to appear at startup. USEFUL! -int currentTableRowIndex = 0; -Table_CSV playbackData_table; +String sdData_fname = "N/A"; //only used if loading input data from a sd file int nextPlayback_millis = -100; //any negative number -// Initialize boards for constants -Cyton cyton = new Cyton(); //dummy creation to get access to constants, create real one later -Ganglion ganglion = new Ganglion(); //dummy creation to get access to constants, create real one later +// Initialize board +DataSource currentBoard = new BoardNull(); + +DataLogger dataLogger = new DataLogger(); + // Intialize interface protocols InterfaceSerial iSerial = new InterfaceSerial(); -Hub hub = new Hub(); //dummy creation to get access to constants, create real one later - String openBCI_portName = "N/A"; //starts as N/A but is selected from control panel to match your OpenBCI USB Dongle's serial/COM int openBCI_baud = 115200; //baud rate from the Arduino @@ -124,34 +126,21 @@ String ganglion_portName = "N/A"; String wifi_portName = "N/A"; String wifi_ipAddress = "192.168.4.1"; -final static String PROTOCOL_BLE = "ble"; -final static String PROTOCOL_BLED112 = "bled112"; -final static String PROTOCOL_SERIAL = "serial"; -final static String PROTOCOL_WIFI = "wifi"; - ////// ---- Define variables related to OpenBCI board operations //Define number of channels from cyton...first EEG channels, then aux channels int nchan = NCHAN_CYTON; //Normally, 8 or 16. Choose a smaller number to show fewer on the GUI -int n_aux_ifEnabled = 3; // this is the accelerometer data CHIP 2014-11-03 + //define variables related to warnings to the user about whether the EEG data is nearly railed (and, therefore, of dubious quality) DataStatus is_railed[]; final int threshold_railed = int(pow(2, 23)-1000); //fully railed should be +/- 2^23, so set this threshold close to that value final int threshold_railed_warn = int(pow(2, 23)*0.9); //set a somewhat smaller value as the warning threshold -//OpenBCI SD Card setting (if eegDataSource == 0) -int sdSetting = 0; //0 = do not write; 1 = 5 min; 2 = 15 min; 3 = 30 min; etc... -String sdSettingString = "Do not write to SD"; -//cyton data packet -int nDataBackBuff; -DataPacket_ADS1299 dataPacketBuff[]; //allocate later in InitSystem -int curDataPacketInd = -1; -int curBDFDataPacketInd = -1; -int lastReadDataPacketInd = -1; -////// ---- End variables related to the OpenBCI boards - -// define some timing variables for this program's operation -long timeOfLastFrame = 0; -long timeOfInit; -boolean attemptingToConnect = false; + +//Cyton SD Card setting +CytonSDMode cyton_sdSetting = CytonSDMode.NO_WRITE; + +//NovaXR Default Settings +NovaXRMode novaXR_boardSetting = NovaXRMode.DEFAULT; //default mode +NovaXRSR novaXR_sampleRate = NovaXRSR.SR_250; // Calculate nPointsPerUpdate based on sampling rate and buffer update rate // @UPDATE_MILLIS: update the buffer every 40 milliseconds @@ -159,26 +148,15 @@ boolean attemptingToConnect = false; // The sampling rate should be ideally a multiple of 25, so as to make actual buffer update rate exactly 40ms final int UPDATE_MILLIS = 40; int nPointsPerUpdate; // no longer final, calculate every time in initSystem -// final int nPointsPerUpdate = 50; //update the GUI after this many data points have been received -// final int nPointsPerUpdate = 24; //update the GUI after this many data points have been received -// final int nPointsPerUpdate = 10; //update the GUI after this many data points have been received //define some data fields for handling data here in processing -float dataBuffX[]; //define the size later -float dataBuffY_uV[][]; //2D array to handle multiple data channels, each row is a new channel so that dataBuffY[3][] is channel 4 -float dataBuffY_filtY_uV[][]; -float yLittleBuff[]; -float yLittleBuff_uV[][]; //small buffer used to send data to the filters -float accelerometerBuff[][]; // accelerometer buff 500 points -float auxBuff[][]; +float dataProcessingRawBuffer[][]; //2D array to handle multiple data channels, each row is a new channel so that dataBuffY[3][] is channel 4 +float dataProcessingFilteredBuffer[][]; float data_elec_imp_ohm[]; -float displayTime_sec = 20f; //define how much time is shown on the time-domain montage plot (and how much is used in the FFT plot?) -float dataBuff_len_sec = displayTime_sec + 3f; //needs to be wider than actual display so that filter startup is hidden +int displayTime_sec = 20; //define how much time is shown on the time-domain montage plot (and how much is used in the FFT plot?) +int dataBuff_len_sec = displayTime_sec + 3; //needs to be wider than actual display so that filter startup is hidden -//variables for writing EEG data out to a file -OutputFile_rawtxt fileoutput_odf; -OutputFile_BDF fileoutput_bdf; String output_fname; String sessionName = "N/A"; final int OUTPUT_SOURCE_NONE = 0; @@ -187,18 +165,22 @@ final int OUTPUT_SOURCE_BDF = 2; // The BDF data format http://www.biosemi.com/f public int outputDataSource = OUTPUT_SOURCE_ODF; // public int outputDataSource = OUTPUT_SOURCE_BDF; +//Used mostly in W_playback.pde +JSONObject savePlaybackHistoryJSON; +JSONObject loadPlaybackHistoryJSON; +String userPlaybackHistoryFile; +boolean playbackHistoryFileExists = false; +String playbackData_ShortName; +boolean recentPlaybackFilesHaveUpdated = false; + // Serial output -String serial_output_portName = "/dev/tty.usbmodem1421"; //must edit this based on the name of the serial/COM port Serial serial_output; -int serial_output_baud = 9600; //baud rate from the Arduino //Control Panel for (re)configuring system settings PlotFontInfo fontInfo; //program variables boolean isRunning = false; -boolean redrawScreenNow = true; -int openBCI_byteCount = 0; StringBuilder board_message; //set window size @@ -233,18 +215,7 @@ PFont p6; //small Open Sans ButtonHelpText buttonHelpText; -//Used for playback file -boolean has_processed = false; -boolean isOldData = false; -boolean playbackFileIsEmpty = false; -int indices = 0; -//# columns used by a playback file determines number of channels -final int totalColumns4ChanThresh = 10; -final int totalColumns16ChanThresh = 16; - boolean setupComplete = false; -boolean isHubInitialized = false; -boolean isHubObjectInitialized = false; color bgColor = color(1, 18, 41); color openbciBlue = color(31, 69, 110); int COLOR_SCHEME_DEFAULT = 1; @@ -252,14 +223,6 @@ int COLOR_SCHEME_ALTERNATIVE_A = 2; // int COLOR_SCHEME_ALTERNATIVE_B = 3; int colorScheme = COLOR_SCHEME_ALTERNATIVE_A; -Process nodeHubby; -String nodeHubName = "OpenBCIHub"; -Timer hubTimer = new Timer(true); -boolean hubTimerHasStarted = false; -int hubTimerCounter; //Count how many times GUI tries to connect to Hub -int hubTimerLimit = 8; //Allow up to 8 tries for GUI to connect to Hub -int hubTimerInterval = 2500; //try every 2.5 seconds, 8*2.5=20seconds - PApplet ourApplet; static CustomOutputStream outputStream; @@ -268,12 +231,8 @@ static CustomOutputStream outputStream; public final static String stopButton_pressToStop_txt = "Stop Data Stream"; public final static String stopButton_pressToStart_txt = "Start Data Stream"; -///////////Variables from HardwareSettingsController. This fixes a number of issues. -int numSettingsPerChannel = 6; //each channel has 6 different settings -char[][] channelSettingValues = new char [nchan][numSettingsPerChannel]; // [channel#][Button#-value] ... this will incfluence text of button -char[][] impedanceCheckValues = new char [nchan][2]; - -SoftwareSettings settings = new SoftwareSettings(); +SessionSettings settings; +DirectoryManager directoryManager; //------------------------------------------------------------------------ // Global Functions @@ -284,6 +243,9 @@ SoftwareSettings settings = new SoftwareSettings(); int frameRateCounter = 1; //0 = 24, 1 = 30, 2 = 45, 3 = 60 void settings() { + //LINUX GFX FIX #816 + System.setProperty("jogl.disable.openglcore", "false"); + // If 1366x768, set GUI to 976x549 to fix #378 regarding some laptop resolutions // Later changed to 976x742 so users can access full control panel if (displayWidth == 1366 && displayHeight == 768) { @@ -329,6 +291,8 @@ void setup() { "If this error persists, contact the OpenBCI team for support."; return; // early exit } + + directoryManager = new DirectoryManager(); // redirect all output to a custom stream that will intercept all prints // write them to file and display them in the GUI's console window @@ -336,10 +300,15 @@ void setup() { System.setOut(outputStream); System.setErr(outputStream); - println("Console Log Started at Local Time: " + getDateString()); + println("Console Log Started at Local Time: " + directoryManager.getFileNameDateTime()); println("Screen Resolution: " + displayWidth + " X " + displayHeight); println("Welcome to the Processing-based OpenBCI GUI!"); //Welcome line. println("For more information, please visit: https://openbci.github.io/Documentation/docs/06Software/01-OpenBCISoftware/GUIDocs"); + + // Copy sample data to the Users' Documents folder + create Recordings folder + directoryManager.init(); + settings = new SessionSettings(); + userPlaybackHistoryFile = directoryManager.getSettingsPath()+"UserPlaybackHistory.json"; //open window ourApplet = this; @@ -364,9 +333,6 @@ void setup() { } void delayedSetup() { - if (!isWindows()) hubStop(); //kill any existing hubs before starting a new one.. - hubInit(); // putting down here gives windows time to close any open apps - smooth(); //turn this off if it's too slow surface.setResizable(true); //updated from frame.setResizable in Processing 2 @@ -379,7 +345,6 @@ void delayedSetup() { frame.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { if (e.getSource()==frame) { - println("OpenBCI_GUI: setup: RESIZED"); settings.screenHasBeenResized = true; settings.timeOfLastScreenResize = millis(); // initializeGUI(); @@ -405,10 +370,7 @@ void delayedSetup() { buttonHelpText = new ButtonHelpText(); - myPresentation = new Presentation(); - - // Create GUI data folder and copy sample data if meditation file doesn't exist - copyGUISampleData(); + prepareExitHandler(); synchronized(this) { // Instantiate ControlPanel in the synchronized block. @@ -421,44 +383,6 @@ void delayedSetup() { } } -public void copyGUISampleData(){ - String directoryName = settings.guiDataPath + File.separator + "Sample_Data" + File.separator; - String fileToCheckString = directoryName + "OpenBCI-sampleData-2-meditation.txt"; - File directory = new File(directoryName); - File fileToCheck = new File(fileToCheckString); - if (!fileToCheck.exists()){ - println("OpenBCI_GUI::Setup: Copying sample data to Documents/OpenBCI_GUI/Sample_Data"); - // Make the entire directory path including parents - directory.mkdirs(); - try { - List results = new ArrayList(); - File[] filesFound = new File(dataPath("EEG_Sample_Data")).listFiles(); - //If this pathname does not denote a directory, then listFiles() returns null. - for (File file : filesFound) { - if (file.isFile()) { - results.add(file); - } - } - for(File file : results) { - Files.copy(file.toPath(), - (new File(directoryName + file.getName())).toPath(), - StandardCopyOption.REPLACE_EXISTING); - } - } catch (IOException e) { - outputError("Setup: Error trying to copy Sample Data to Documents directory."); - } - } else { - println("OpenBCI_GUI::Setup: Sample Data exists in Documents folder."); - } - - //Create \Documents\OpenBCI_GUI\Recordings\ if it doesn't exist - String recordingDirString = settings.guiDataPath + File.separator + "Recordings"; - File recDirectory = new File(recordingDirString); - if (recDirectory.mkdir()) { - println("OpenBCI_GUI::Setup: Created \\Documents\\OpenBCI_GUI\\Recordings\\"); - } -} - //====================== END-OF-SETUP ==========================// //======================== DRAW LOOP =============================// @@ -467,7 +391,6 @@ synchronized void draw() { if (showStartupError) { drawStartupError(); } else if (setupComplete && systemMode != SYSTEMMODE_INTROANIMATION) { - drawLoop_counter++; //signPost("10"); systemUpdate(); //signPost("20"); systemDraw(); //signPost("30"); if (midInit) { @@ -475,6 +398,11 @@ synchronized void draw() { //When Init session is started, the screen will seem to hang. systemInitSession(); } + if(reinitRequested) { + haltSystem(); + initSystem(); + reinitRequested = false; + } } else if (systemMode == SYSTEMMODE_INTROANIMATION) { if (settings.introAnimationInit == 0) { settings.introAnimationInit = millis(); @@ -486,190 +414,22 @@ synchronized void draw() { //====================== END-OF-DRAW ==========================// -/** - * This allows us to kill the running node process on quit. - */ private void prepareExitHandler () { + // This callback will run when the GUI quits Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run () { System.out.println("SHUTDOWN HOOK"); - //If user starts system and quits the app, - //save user settings for current mode! - try { - if (systemMode == SYSTEMMODE_POSTINIT) { - settings.save(settings.getPath("User", eegDataSource, nchan)); - } - } catch (NullPointerException e) { - e.printStackTrace(); - } - //Close network streams - if (w_networking != null && w_networking.getNetworkActive()) { - w_networking.stopNetwork(); - println("openBCI_GUI: shutDown: Network streams stopped"); - } - //Shutdown the hub - try { - if (hubStop()) { - System.out.println("SHUTDOWN HUB"); - } else { - System.out.println("FAILED TO SHUTDOWN HUB"); - } - - } catch (Exception ex) { - ex.printStackTrace(); // not much else to do at this point - } + + haltSystem(); } } )); } -/** - * Starts the hub and sets prepares the exit handler. - */ -void hubInit() { - isHubInitialized = true; - hubStart(); - prepareExitHandler(); -} - -/** - * Starts the node hub working, tested on mac and windows. - */ -void hubStart() { - println("Launching application from local data dir"); - try { - // https://forum.processing.org/two/discussion/13053/use-launch-for-applications-kept-in-data-folder - if (isWindows()) { - println("OpenBCI_GUI: hubStart: OS Detected: Windows"); - nodeHubby = launch(dataPath("/OpenBCIHub/OpenBCIHub.exe")); - } else if (isLinux()) { - println("OpenBCI_GUI: hubStart: OS Detected: Linux"); - nodeHubby = exec(dataPath("./OpenBCIHub/OpenBCIHub")); - } else { - println("OpenBCI_GUI: hubStart: OS Detected: Mac"); - nodeHubby = launch(dataPath("OpenBCIHub.app")); - } - // hubRunning = true; - } - catch (Exception e) { - println("hubStart: " + e); - } -} - -/** - * @description Single function to call at the termination program hook. - */ -boolean hubStop() { - if (isWindows()) { - return killRunningprocessWin(); - } else { - killRunningProcessMac(); - return true; - } -} - -/** - * @description Helper function to determine if the system is linux or not. - * @return {boolean} true if os is linux, false otherwise. - */ -private boolean isLinux() { - return System.getProperty("os.name").toLowerCase().indexOf("linux") > -1; -} - -/** - * @description Helper function to determine if the system is windows or not. - * @return {boolean} true if os is windows, false otherwise. - */ -private boolean isWindows() { - return System.getProperty("os.name").toLowerCase().indexOf("windows") > -1; -} - -/** - * @description Helper function to determine if the system is macOS or not. - * @return {boolean} true if os is windows, false otherwise. - */ -private boolean isMac() { - return !isWindows() && !isLinux(); -} - -/** - * @description Parses the running process list for processes whose name have ganglion hub, if found, kills them one by one. - * function dubbed "death dealer" - */ -void killRunningProcessMac() { - try { - String line; - Process p = Runtime.getRuntime().exec("ps -e"); - BufferedReader input = - new BufferedReader(new InputStreamReader(p.getInputStream())); - while ((line = input.readLine()) != null) { - if (line.contains(nodeHubName)) { - try { - endProcess(getProcessIdFromLineMac(line)); - println("Killed: " + line); - } - catch (Exception err) { - println("Failed to stop process: " + line + "\n\n"); - err.printStackTrace(); - } - } - } - input.close(); - } - catch (Exception err) { - err.printStackTrace(); - } -} - -/** - * @description Parses the running process list for processes whose name have ganglion hub, if found, kills them one by one. - * function dubbed "death dealer" aka "cat killer" - */ -boolean killRunningprocessWin() { - try { - Runtime.getRuntime().exec("taskkill /F /IM OpenBCIHub.exe"); - return true; - } - catch (Exception err) { - err.printStackTrace(); - return false; - } -} - -/** - * @description Parses a mac process line and grabs the pid, the first component. - * @return {int} the process id - */ -int getProcessIdFromLineMac(String line) { - line = trim(line); - String[] components = line.split(" "); - return Integer.parseInt(components[0]); -} - -void endProcess(int pid) { - Runtime rt = Runtime.getRuntime(); - try { - rt.exec("kill -9 " + pid); - } - catch (IOException err) { - err.printStackTrace(); - } -} - -int pointCounter = 0; -int prevBytes = 0; -int prevMillis = millis(); -int byteRate_perSec = 0; -int drawLoop_counter = 0; - //used to init system based on initial settings...Called from the "START SESSION" button in the GUI's ControlPanel -void setupWidgetManager() { - wm = new WidgetManager(this); -} - //Initialize the system -void initSystem() throws Exception { +void initSystem() { println(""); println(""); println("================================================="); @@ -677,76 +437,94 @@ void initSystem() throws Exception { println("================================================="); println(""); - timeOfInit = millis(); //store this for timeout in case init takes too long - verbosePrint("OpenBCI_GUI: initSystem: -- Init 0 -- " + timeOfInit); - //Checking status here causes "error: resource busy" during init - /* - if (eegDataSource == DATASOURCE_CYTON) { - verbosePrint("OpenBCI_GUI: initSystem: Checking Cyton Connection..."); - system_status(rcBox); - if (rcStringReceived.startsWith("Cyton dongle could not connect") || rcStringReceived.startsWith("Failure")) { - throw new Exception("OpenBCI_GUI: initSystem: Dongle failed to connect to Cyton..."); - } - } - */ - verbosePrint("OpenBCI_GUI: initSystem: Preparing data variables..."); - //initialize playback file if necessary - if (eegDataSource == DATASOURCE_PLAYBACKFILE) { - initPlaybackFileToTable(); //found in W_Playback.pde - } - verbosePrint("OpenBCI_GUI: initSystem: Initializing core data objects"); - initCoreDataObjects(); - - verbosePrint("OpenBCI_GUI: initSystem: -- Init 1 -- " + millis()); - verbosePrint("OpenBCI_GUI: initSystem: Initializing FFT data objects"); - initFFTObjectsAndBuffer(); - - //prepare some signal processing stuff - //for (int Ichan=0; Ichan < nchan; Ichan++) { detData_freqDomain[Ichan] = new DetectionData_FreqDomain(); } + verbosePrint("OpenBCI_GUI: initSystem: -- Init 0 -- "); - verbosePrint("OpenBCI_GUI: initSystem: -- Init 2 -- " + millis()); - verbosePrint("OpenBCI_GUI: initSystem: Closing ControlPanel..."); + if (initSystemButton.but_txt == "START SESSION") { + initSystemButton.but_txt = "STOP SESSION"; + } - controlPanel.close(); - topNav.controlPanelCollapser.setIsActive(false); - verbosePrint("OpenBCI_GUI: initSystem: Initializing comms with hub...."); - hub.changeState(HubState.COMINIT); - // hub.searchDeviceStop(); + //reset init variables + systemHasHalted = false; + boolean abandonInit = false; //prepare the source of the input data switch (eegDataSource) { case DATASOURCE_CYTON: - int nEEDataValuesPerPacket = nchan; - boolean useAux = true; - if (cyton.getInterface() == INTERFACE_SERIAL) { - cyton = new Cyton(this, openBCI_portName, openBCI_baud, nEEDataValuesPerPacket, useAux, n_aux_ifEnabled, cyton.getInterface()); //this also starts the data transfer after XX seconds - } else { - if (hub.getWiFiStyle() == WIFI_DYNAMIC) { - cyton = new Cyton(this, wifi_portName, openBCI_baud, nEEDataValuesPerPacket, useAux, n_aux_ifEnabled, cyton.getInterface()); //this also starts the data transfer after XX seconds - } else { - cyton = new Cyton(this, wifi_ipAddress, openBCI_baud, nEEDataValuesPerPacket, useAux, n_aux_ifEnabled, cyton.getInterface()); //this also starts the data transfer after XX seconds + if (selectedProtocol == BoardProtocol.SERIAL) { + if(nchan == 16) { + currentBoard = new BoardCytonSerialDaisy(openBCI_portName); + } + else { + currentBoard = new BoardCytonSerial(openBCI_portName); + } + } + else if (selectedProtocol == BoardProtocol.WIFI) { + if(nchan == 16) { + currentBoard = new BoardCytonWifiDaisy(wifi_ipAddress, selectedSamplingRate); + } + else { + currentBoard = new BoardCytonWifi(wifi_ipAddress, selectedSamplingRate); } } break; case DATASOURCE_SYNTHETIC: - //do nothing + currentBoard = new BoardBrainFlowSynthetic(nchan); break; case DATASOURCE_PLAYBACKFILE: - break; - case DATASOURCE_GANGLION: - if (ganglion.getInterface() == INTERFACE_HUB_BLE || ganglion.getInterface() == INTERFACE_HUB_BLED112) { - hub.connectBLE(ganglion_portName); + if (!playbackData_fname.equals("N/A")) { + currentBoard = new DataSourcePlayback(playbackData_fname); } else { - if (hub.getWiFiStyle() == WIFI_DYNAMIC) { - hub.connectWifi(wifi_portName); - } else { - hub.connectWifi(wifi_ipAddress); + if (!sdData_fname.equals("N/A")) { + currentBoard = new DataSourceSDCard(sdData_fname); + } + else { + // no code path to it + println("No playback or SD file selected."); } } break; + case DATASOURCE_GANGLION: + if (selectedProtocol == BoardProtocol.WIFI) { + currentBoard = new BoardGanglionWifi(wifi_ipAddress, selectedSamplingRate); + } + else { + // todo[brainflow] temp hardcode + String ganglionName = (String)(bleList.getItem(bleList.activeItem).get("headline")); + String ganglionPort = (String)(bleList.getItem(bleList.activeItem).get("subline")); + String ganglionMac = BLEMACAddrMap.get(ganglionName); + println("MAC address for Ganglion is " + ganglionMac); + currentBoard = new BoardGanglionBLE(ganglionPort, ganglionMac); + } + break; + case DATASOURCE_NOVAXR: + currentBoard = new BoardNovaXR(novaXR_boardSetting, novaXR_sampleRate); + // Replace line above with line below to test brainflow synthetic + //currentBoard = new BoardBrainFlowSynthetic(); + break; default: break; - } + } + + // initialize the chosen board + boolean success = currentBoard.initialize(); + abandonInit = !success; // abandon if init fails + + updateToNChan(currentBoard.getNumEXGChannels()); + + dataLogger.initialize(); + + verbosePrint("OpenBCI_GUI: initSystem: Initializing core data objects"); + initCoreDataObjects(); + + verbosePrint("OpenBCI_GUI: initSystem: -- Init 1 -- " + millis()); + verbosePrint("OpenBCI_GUI: initSystem: Initializing FFT data objects"); + initFFTObjectsAndBuffer(); + + verbosePrint("OpenBCI_GUI: initSystem: -- Init 2 -- " + millis()); + verbosePrint("OpenBCI_GUI: initSystem: Closing ControlPanel..."); + + controlPanel.close(); + topNav.controlPanelCollapser.setIsActive(false); verbosePrint("OpenBCI_GUI: initSystem: -- Init 3 -- " + millis()); @@ -758,15 +536,13 @@ void initSystem() throws Exception { //initilize the GUI topNav.initSecondaryNav(); - setupWidgetManager(); + wm = new WidgetManager(this); if (!abandonInit) { nextPlayback_millis = millis(); //used for synthesizeData and readFromFile. This restarts the clock that keeps the playback at the right pace. - w_timeSeries.hsc.loadDefaultChannelSettings(); - if (eegDataSource != DATASOURCE_GANGLION && eegDataSource != DATASOURCE_CYTON) { - systemMode = SYSTEMMODE_POSTINIT; //tell system it's ok to leave control panel and start interfacing GUI - } + systemMode = SYSTEMMODE_POSTINIT; //tell system it's ok to leave control panel and start interfacing GUI + if (!abandonInit) { controlPanel.close(); } else { @@ -782,59 +558,19 @@ void initSystem() throws Exception { } verbosePrint("OpenBCI_GUI: initSystem: -- Init 4 -- " + millis()); - - if (eegDataSource == DATASOURCE_CYTON) { - if (hub.getFirmwareVersion() == null && hub.getProtocol().equals(PROTOCOL_WIFI)) { - println("Cyton+WiFi: Unable to find board firmware version"); - } else if (hub.getFirmwareVersion().equals("v1.0.0")) { - //this means the firmware is very out of date, and commands may not work, so abandon init - abandonInit = true; - } else { - //println("FOUND FIRMWARE FROM HUB == " + hub.getFirmwareVersion()); - } - } - if (!abandonInit) { - //Init software settings: create default settings files, load user settings, etc. + + if (eegDataSource != DATASOURCE_NOVAXR) { //don't save default settings for NovaXR + //Init software settings: create default settings file that is datasource unique settings.init(); settings.initCheckPointFive(); - } else { - haltSystem(); - if (eegDataSource == DATASOURCE_CYTON) { - //Normally, this message appears if you have a dongle plugged in, and the Cyton is not On, or on the wrong channel. - if (cyton.daisyNotAttached) { - outputError("Daisy is not attached to the Cyton board. Check connection or select 8 Channels."); - } else { - outputError("Check that the device is powered on and in range. Also, try AUTOSCAN. Otherwise, Cyton firmware is out of date."); - } - } else { - outputError("Failed to connect. Check that the device is powered on and in range."); - } - controlPanel.open(); - systemMode = SYSTEMMODE_PREINIT; // leave this here } - //reset init variables - cyton.daisyNotAttached = false; midInit = false; - abandonInit = false; - systemHasHalted = false; } //end initSystem -/** - * @description Useful function to get the correct sample rate based on data source - * @returns `float` - The frequency / sample rate of the data source - */ -float getSampleRateSafe() { - if (eegDataSource == DATASOURCE_GANGLION) { - return ganglion.getSampleRate(); - } else if (eegDataSource == DATASOURCE_CYTON) { - return cyton.getSampleRate(); - } else if (eegDataSource == DATASOURCE_PLAYBACKFILE) { - return playbackData_table.getSampleRate(); - } else { - return 250; - } +public int getCurrentBoardBufferSize() { + return dataBuff_len_sec * currentBoard.getSampleRate(); } /** @@ -842,8 +578,10 @@ float getSampleRateSafe() { * @returns `int` - Points of FFT. 125Hz, 200Hz, 250Hz -> 256points. 1000Hz -> 1024points. 1600Hz -> 2048 points. */ int getNfftSafe() { - int sampleRate = (int)getSampleRateSafe(); + int sampleRate = currentBoard.getSampleRate(); switch (sampleRate) { + case 500: + return 512; case 1000: return 1024; case 1600: @@ -857,47 +595,30 @@ int getNfftSafe() { } void initCoreDataObjects() { - // Nfft = getNfftSafe(); - nDataBackBuff = 3*(int)getSampleRateSafe(); - dataPacketBuff = new DataPacket_ADS1299[nDataBackBuff]; // call the constructor here - nPointsPerUpdate = int(round(float(UPDATE_MILLIS) * getSampleRateSafe()/ 1000.f)); - dataBuffX = new float[(int)(dataBuff_len_sec * getSampleRateSafe())]; - dataBuffY_uV = new float[nchan][dataBuffX.length]; - dataBuffY_filtY_uV = new float[nchan][dataBuffX.length]; - yLittleBuff = new float[nPointsPerUpdate]; - yLittleBuff_uV = new float[nchan][nPointsPerUpdate]; //small buffer used to send data to the filters - auxBuff = new float[3][nPointsPerUpdate]; - accelerometerBuff = new float[3][500]; // 500 points = 25Hz * 20secs(Max Window) - for (int i=0; i OUTPUT_SOURCE_NONE && eegDataSource < DATASOURCE_PLAYBACKFILE) { - //open data file if it has not already been opened - if (!settings.isLogFileOpen()) { - if (eegDataSource == DATASOURCE_CYTON) openNewLogFile(getDateString()); - if (eegDataSource == DATASOURCE_GANGLION) openNewLogFile(getDateString()); - } - settings.setLogFileStartTime(System.nanoTime()); - } } } //halt the data collection void haltSystem() { - if (!systemHasHalted) { //prevents system from halting more than once + if (!systemHasHalted) { //prevents system from halting more than once\ println("openBCI_GUI: haltSystem: Halting system for reconfiguration of settings..."); if (initSystemButton.but_txt == "STOP SESSION") { initSystemButton.but_txt = "START SESSION"; @@ -1005,108 +695,38 @@ void haltSystem() { settings.save(settings.getPath("User", eegDataSource, nchan)); } - if(cyton.isPortOpen()) { //On halt and the port is open, reset board mode to Default. - if (w_pulsesensor.analogReadOn || w_analogRead.analogReadOn) { - cyton.setBoardMode(BoardMode.DEFAULT); - output("Starting to read accelerometer"); - w_pulsesensor.analogModeButton.setString("Turn Analog Read On"); - w_pulsesensor.analogReadOn = false; - w_analogRead.analogModeButton.setString("Turn Analog Read On"); - w_analogRead.analogReadOn = false; - } else if (w_digitalRead.digitalReadOn) { - cyton.setBoardMode(BoardMode.DEFAULT); - output("Starting to read accelerometer"); - w_digitalRead.digitalModeButton.setString("Turn Digital Read On"); - w_digitalRead.digitalReadOn = false; - } else if (w_markermode.markerModeOn) { - cyton.setBoardMode(BoardMode.DEFAULT); - output("Starting to read accelerometer"); - w_markermode.markerModeButton.setString("Turn Marker On"); - w_markermode.markerModeOn = false; - } - } - - //reset variables for data processing - curDataPacketInd = -1; - lastReadDataPacketInd = -1; - pointCounter = 0; - currentTableRowIndex = 0; - prevBytes = 0; - prevMillis = millis(); - byteRate_perSec = 0; - drawLoop_counter = 0; - // eegDataSource = -1; - //set all data source list items inactive - - //Fix issue for processing successive playback files - indices = 0; - hasRepeated = false; - has_processed = false; - settings.settingsLoaded = false; //on halt, reset this value - //reset connect loadStrings openBCI_portName = "N/A"; // Fixes inability to reconnect after halding JAM 1/2017 - ganglion_portName = "N/A"; - wifi_portName = "N/A"; + ganglion_portName = ""; + wifi_portName = ""; controlPanel.resetListItems(); - if (eegDataSource == DATASOURCE_CYTON) { - closeLogFile(); //close log file - cyton.closeSDandPort(); - } else if (eegDataSource == DATASOURCE_GANGLION) { - if(ganglion.isCheckingImpedance()) { - ganglion.impedanceStop(); - w_ganglionImpedance.startStopCheck.but_txt = "Start Impedance Check"; - } - closeLogFile(); //close log file - ganglion.impedanceArray = new int[NCHAN_GANGLION + 1]; - ganglion.closePort(); - } else if (eegDataSource == DATASOURCE_PLAYBACKFILE) { + if (eegDataSource == DATASOURCE_PLAYBACKFILE) { controlPanel.recentPlaybackBox.getRecentPlaybackFiles(); } systemMode = SYSTEMMODE_PREINIT; - hub.changeState(HubState.NOCOM); recentPlaybackFilesHaveUpdated = false; - // bleList.items.clear(); - // wifiList.items.clear(); + dataLogger.uninitialize(); - // if (ganglion.isBLE() || ganglion.isWifi() || cyton.isWifi()) { - // hub.searchDeviceStart(); - // } + currentBoard.uninitialize(); + currentBoard = new BoardNull(); // back to null systemHasHalted = true; } } //end of halt system void systemUpdate() { // for updating data values and variables - - //Instantiate Hub Object, wait until next step to try to startTCPClient - if (isHubInitialized && isHubObjectInitialized == false) { - hub = new Hub(this); - println("Instantiating hub object..."); - isHubObjectInitialized = true; - } - //Then, immediately start trying to connect to Hub for X seconds - if (!hub.isHubRunning()) { - if (!hubTimerHasStarted) { - hubTimer.schedule(new CheckHubInit(), 0, hubTimerInterval); - hubTimerHasStarted = true; - } else { - if (hubTimerCounter == hubTimerLimit) { - hubTimer.cancel(); - outputError("Unable to find or connect to Hub. LIVE functionality will be disabled."); - hubTimerCounter = 0; - } - } - } - //prepare for updating the GUI win_x = width; win_y = height; + currentBoard.update(); + + dataLogger.update(); + helpWidget.update(); topNav.update(); if (systemMode == SYSTEMMODE_PREINIT) { @@ -1122,50 +742,10 @@ void systemUpdate() { // for updating data values and variables } } if (systemMode == SYSTEMMODE_POSTINIT) { - if (isRunning) { - //get the data, if it is available - pointCounter = getDataIfAvailable(pointCounter); - - //has enough data arrived to process it and update the GUI? - if (pointCounter >= nPointsPerUpdate) { - //reset for next time - pointCounter = 0; - //process the data - processNewData(); - //set this flag to true, checked at the beginning of systemDraw() - redrawScreenNow=true; - } else { - //not enough data has arrived yet... only update the channel controller - } - - //New feature to address #461, defined in DataLogging.pde - //Applied to OpenBCI Data Format for LIVE mode recordings (Cyton and Ganglion) - //Don't check duration if user has selected "No Limit" - if (outputDataSource == OUTPUT_SOURCE_ODF - && eegDataSource < DATASOURCE_PLAYBACKFILE - && settings.limitOBCILogFileDuration()) { - fileoutput_odf.limitRecordingFileDuration(); - } - - } else if (eegDataSource == DATASOURCE_PLAYBACKFILE && !has_processed && !isOldData) { - lastReadDataPacketInd = 0; - pointCounter = 0; - try { - process_input_file(); - println("^^^GUI update process file has occurred"); - } - catch(Exception e) { - isOldData = true; - println("^^^Error processing timestamps"); - output("Error processing timestamps, are you using old data?"); - } - } - - // gui.cc.update(); //update Channel Controller even when not updating certain parts of the GUI... (this is a bit messy...) - + processNewData(); + //alternative component listener function (line 177 - 187 frame.addComponentListener) for processing 3, if (settings.widthOfLastScreen != width || settings.heightOfLastScreen != height) { - println("OpenBCI_GUI: setup: RESIZED"); settings.screenHasBeenResized = true; settings.timeOfLastScreenResize = millis(); settings.widthOfLastScreen = width; @@ -1179,11 +759,6 @@ void systemUpdate() { // for updating data values and variables topNav.screenHasBeenResized(width, height); wm.screenResized(); } - if (settings.screenHasBeenResized == true && (millis() - settings.timeOfLastScreenResize) > settings.reinitializeGUIdelay) { - settings.screenHasBeenResized = false; - println("systemUpdate: reinitializing GUI"); - settings.timeOfGUIreinitialize = millis(); - } if (wm.isWMInitialized) { wm.update(); @@ -1198,62 +773,38 @@ void systemDraw() { //for drawing to the screen //background(255); //clear the screen if (systemMode >= SYSTEMMODE_POSTINIT) { - int drawLoopCounter_thresh = 100; - if ((redrawScreenNow) || (drawLoop_counter >= drawLoopCounter_thresh)) { - //if (drawLoop_counter >= drawLoopCounter_thresh) println("OpenBCI_GUI: redrawing based on loop counter..."); - drawLoop_counter=0; //reset for next time - redrawScreenNow = false; //reset for next time - - //update the title of the figure; - switch (eegDataSource) { - case DATASOURCE_CYTON: - switch (outputDataSource) { - case OUTPUT_SOURCE_ODF: - if (fileoutput_odf != null) { - surface.setTitle(int(frameRate) + " fps, " + int(float(fileoutput_odf.getRowsWritten())/getSampleRateSafe()) + " secs Saved, Writing to " + output_fname); - } - break; - case OUTPUT_SOURCE_BDF: - if (fileoutput_bdf != null) { - surface.setTitle(int(frameRate) + " fps, " + int(fileoutput_bdf.getRecordsWritten()) + " secs Saved, Writing to " + output_fname); - } - break; - case OUTPUT_SOURCE_NONE: - default: - surface.setTitle(int(frameRate) + " fps"); - break; - } - break; - case DATASOURCE_SYNTHETIC: - surface.setTitle(int(frameRate) + " fps, Using Synthetic EEG Data"); + //update the title of the figure; + switch (eegDataSource) { + case DATASOURCE_CYTON: + switch (outputDataSource) { + case OUTPUT_SOURCE_ODF: + surface.setTitle(int(frameRate) + " fps, " + (int)dataLogger.getSecondsWritten() + " secs Saved, Writing to " + output_fname); break; - case DATASOURCE_PLAYBACKFILE: - surface.setTitle(int(frameRate) + " fps, Playing " + getElapsedTimeInSeconds(currentTableRowIndex) + " of " + int(float(playbackData_table.getRowCount())/getSampleRateSafe()) + " secs, Reading from: " + playbackData_fname); + case OUTPUT_SOURCE_BDF: + surface.setTitle(int(frameRate) + " fps, " + (int)dataLogger.getSecondsWritten() + " secs Saved, Writing to " + output_fname); break; - case DATASOURCE_GANGLION: - surface.setTitle(int(frameRate) + " fps, Ganglion!"); + case OUTPUT_SOURCE_NONE: + default: + surface.setTitle(int(frameRate) + " fps"); break; } + break; + case DATASOURCE_SYNTHETIC: + surface.setTitle(int(frameRate) + " fps, Using Synthetic EEG Data"); + break; + case DATASOURCE_PLAYBACKFILE: + surface.setTitle(int(frameRate) + " fps, Reading from: " + playbackData_fname); + break; + case DATASOURCE_GANGLION: + surface.setTitle(int(frameRate) + " fps, Ganglion!"); + break; + default: + surface.setTitle(int(frameRate) + " fps"); + break; } - //wait 1 second for GUI to reinitialize - if ((millis() - settings.timeOfGUIreinitialize) > settings.reinitializeGUIdelay) { - // println("attempting to draw GUI..."); - try { - // println("GUI DRAW!!! " + millis()); - //draw GUI widgets (visible/invisible) using widget manager - wm.draw(); - } catch (Exception e) { - println(e.getMessage()); - settings.reinitializeGUIdelay = settings.reinitializeGUIdelay * 2; - println("OpenBCI_GUI: systemDraw: New GUI reinitialize delay = " + settings.reinitializeGUIdelay); - } - } else { - //reinitializing GUI after resize - println("OpenBCI_GUI: systemDraw: reinitializing GUI after resize... not drawing GUI"); - } + wm.draw(); - //dataProcessing_user.draw(); drawContainers(); } else { //systemMode != 10 //still print title information about fps @@ -1268,43 +819,23 @@ void systemDraw() { //for drawing to the screen controlPanel.draw(); } + //Draw output window at the bottom of the GUI helpWidget.draw(); } - if ((hub.get_state() == HubState.COMINIT || hub.get_state() == HubState.SYNCWITHHARDWARE) && systemMode == SYSTEMMODE_PREINIT) { - if (!attemptingToConnect) { - output("Attempting to establish a connection with your OpenBCI Board..."); - attemptingToConnect = true; - } - - if (millis() - timeOfInit > settings.initTimeoutThreshold) { - haltSystem(); - initSystemButton.but_txt = "START SESSION"; - output("Init timeout. Verify your Serial/COM Port. Power DOWN/UP your OpenBCI Board & Dongle, then retry Initialization."); - controlPanel.open(); - attemptingToConnect = false; - } - } - - //draw presentation last, bc it is intended to be rendered on top of the GUI ... - if (drawPresentation) { - myPresentation.draw(); - //emg_widget.drawTriggerFeedback(); - //dataProcessing_user.drawTriggerFeedback(); - } - - // use commented code below to verify frameRate and check latency - // println("Time since start: " + millis() + " || Time since last frame: " + str(millis()-timeOfLastFrame)); - // timeOfLastFrame = millis(); - + //Draw button help text close to the top buttonHelpText.draw(); - mouseOutOfBounds(); // to fix + //Draw Session Start overlay on top of everything if (midInit) { drawOverlay(); } } +void requestReinit() { + reinitRequested = true; +} + //Always Called after systemDraw() void systemInitSession() { if (midInitCheck2) { @@ -1312,10 +843,11 @@ void systemInitSession() { try { initSystem(); //found in OpenBCI_GUI.pde } catch (Exception e) { - println(e.getMessage()); + e.printStackTrace(); haltSystem(); } midInitCheck2 = false; + midInit = false; } else { midInitCheck2 = true; } @@ -1377,11 +909,6 @@ void drawStartupError() { popStyle(); } -void openConsole() -{ - ConsoleWindow.display(); -} - void drawOverlay() { //Draw a gray overlay when the Start Session button is pressed pushStyle(); @@ -1400,54 +927,3 @@ void drawOverlay() { text(s, width/2 - textWidth(s)/2, height/2 + 8); popStyle(); } - -//CODE FOR FIXING WEIRD EXIT CRASH ISSUE -- 7/27/16 =========================== -boolean mouseInFrame = false; -boolean windowOriginSet = false; -int appletOriginX = 0; -int appletOriginY = 0; -PVector loc; - -void mouseOutOfBounds() { - if (windowOriginSet && mouseInFrame) { - - try { - if (MouseInfo.getPointerInfo().getLocation().x <= appletOriginX || - MouseInfo.getPointerInfo().getLocation().x >= appletOriginX+width || - MouseInfo.getPointerInfo().getLocation().y <= appletOriginY || - MouseInfo.getPointerInfo().getLocation().y >= appletOriginY+height) { - mouseX = 0; - mouseY = 0; - // println("Mouse out of bounds!"); - mouseInFrame = false; - } - } - catch (RuntimeException e) { - verbosePrint("Error happened while cursor left application..."); - } - } else { - if (mouseX > 0 && mouseX < width && mouseY > 0 && mouseY < height) { - loc = getWindowLocation(P2D); - appletOriginX = (int)loc.x; - appletOriginY = (int)loc.y; - windowOriginSet = true; - mouseInFrame = true; - } - } -} - -PVector getWindowLocation(String renderer) { - PVector l = new PVector(); - if (renderer == P2D || renderer == P3D) { - com.jogamp.nativewindow.util.Point p = new com.jogamp.nativewindow.util.Point(); - ((com.jogamp.newt.opengl.GLWindow)surface.getNative()).getLocationOnScreen(p); - l.x = p.getX(); - l.y = p.getY(); - } else if (renderer == JAVA2D) { - java.awt.Frame f = (java.awt.Frame) ((processing.awt.PSurfaceAWT.SmoothCanvas) surface.getNative()).getFrame(); - l.x = f.getX(); - l.y = f.getY(); - } - return l; -} -//END OF CODE FOR FIXING WEIRD EXIT CRASH ISSUE -- 7/27/16 =========================== diff --git a/OpenBCI_GUI/PPGCapableBoard.pde b/OpenBCI_GUI/PPGCapableBoard.pde new file mode 100644 index 000000000..6dde16922 --- /dev/null +++ b/OpenBCI_GUI/PPGCapableBoard.pde @@ -0,0 +1,9 @@ + +interface PPGCapableBoard { + + public boolean isPPGActive(); + + public void setPPGActive(boolean active); + + public int[] getPPGChannels(); +}; diff --git a/OpenBCI_GUI/PopupMessage.pde b/OpenBCI_GUI/PopupMessage.pde new file mode 100644 index 000000000..789c51c38 --- /dev/null +++ b/OpenBCI_GUI/PopupMessage.pde @@ -0,0 +1,139 @@ +import java.awt.Frame; +import processing.awt.PSurfaceAWT; + +// Instantiate this class to show a popup message + +class PopupMessage extends PApplet implements Runnable { + private final int defaultWidth = 500; + private final int defaultHeight = 250; + + private final int headerHeight = 55; + private final int padding = 20; + + private final int buttonWidth = 100; + private final int buttonHeight = 40; + + private String message = "Empty Popup"; + private String headerMessage = "Error"; + private String buttonMessage = "OK"; + private String buttonLink = null; + + private color headerColor = openbciBlue; + private color buttonColor = openbciBlue; + + private ControlP5 cp5; + + public PopupMessage(String header, String msg) { + super(); + + headerMessage = header; + message = msg; + + Thread t = new Thread(this); + t.start(); + } + + public PopupMessage(String header, String msg, String btnMsg, String btnLink) { + super(); + + headerMessage = header; + message = msg; + buttonMessage = btnMsg; + buttonLink = btnLink; + + Thread t = new Thread(this); + t.start(); + } + + @Override + public void run() { + PApplet.runSketch(new String[] {headerMessage}, this); + } + + @Override + void settings() { + size(defaultWidth, defaultHeight); + } + + @Override + void setup() { + surface.setTitle(headerMessage); + surface.setAlwaysOnTop(true); + surface.setResizable(false); + + cp5 = new ControlP5(this); + + cp5.addButton("onButtonPressed") + .setPosition(width/2 - buttonWidth/2, height - buttonHeight - padding) + .setSize(buttonWidth, buttonHeight) + .setColorLabel(color(255)) + .setColorForeground(buttonColor) + .setColorBackground(buttonColor); + cp5.getController("onButtonPressed") + .getCaptionLabel() + .setFont(createFont("Arial",20,true)) + .toUpperCase(false) + .setSize(20) + .setText(buttonMessage); + } + + @Override + void draw() { + final int w = defaultWidth; + final int h = defaultHeight; + + pushStyle(); + + // draw bg + background(bgColor); + stroke(204); + fill(238); + rect((width - w)/2, (height - h)/2, w, h); + + // draw header + noStroke(); + fill(headerColor); + rect((width - w)/2, (height - h)/2, w, headerHeight); + + //draw header text + textFont(p0, 24); + fill(255); + textAlign(LEFT, CENTER); + text(headerMessage, (width - w)/2 + padding, (height - h)/2, w, headerHeight); + + //draw message + textFont(p3, 16); + fill(102); + textAlign(LEFT, TOP); + text(message, (width - w)/2 + padding, (height - h)/2 + padding + headerHeight, w-padding*2, h-padding*2-headerHeight); + + popStyle(); + + cp5.draw(); + } + + @Override + void mousePressed() { + + } + + @Override + void mouseReleased() { + + } + + @Override + void exit() { + dispose(); + } + + public void onButtonPressed() { + if (buttonLink != null) { + link(buttonLink); + } + noLoop(); + Frame frame = ( (PSurfaceAWT.SmoothCanvas) ((PSurfaceAWT)surface).getNative()).getFrame(); + frame.dispose(); + exit(); + } +}; diff --git a/OpenBCI_GUI/Presentation.pde b/OpenBCI_GUI/Presentation.pde deleted file mode 100644 index 00c0640f0..000000000 --- a/OpenBCI_GUI/Presentation.pde +++ /dev/null @@ -1,97 +0,0 @@ - - -/////////////////////////////////////////////////////////////////////////// -// -// Created: 2/19/16 -// by Conor Russomanno for BodyHacking Con DIY Cyborgia Presentation -// This code is used to organize a neuro-powered presentation... refer to triggers in the EEG_Processing_User class of the EEG_Processing.pde file -// -/////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------ -// Global Variables & Instances -//------------------------------------------------------------------------ - -Presentation myPresentation; -boolean drawPresentation = false; - -//------------------------------------------------------------------------ -// Global Functions -//------------------------------------------------------------------------ - -//------------------------------------------------------------------------ -// Classes -//------------------------------------------------------------------------ - -class Presentation { - //presentation images - int slideCount = 4; - PImage presentationSlides[] = new PImage[slideCount]; - float timeOfLastSlideChange = 0; - int currentSlide = 0; - boolean lockSlides = false; - - Presentation (){ - //loading presentation images - //println("attempting to load images for presentation..."); - presentationSlides[0] = loadImage("prez-images/Presentation.000.jpg"); - presentationSlides[1] = loadImage("prez-images/Presentation.001.jpg"); - presentationSlides[2] = loadImage("prez-images/Presentation.002.jpg"); - presentationSlides[3] = loadImage("prez-images/Presentation.003.jpg"); - // presentationSlides[4] = loadImage("prez-images/Presentation.004.jpg"); - // presentationSlides[5] = loadImage("prez-images/Presentation.005.jpg"); - // presentationSlides[6] = loadImage("prez-images/Presentation.006.jpg"); - // presentationSlides[7] = loadImage("prez-images/Presentation.007.jpg"); - // presentationSlides[8] = loadImage("prez-images/Presentation.008.jpg"); - // presentationSlides[9] = loadImage("prez-images/Presentation.009.jpg"); - // presentationSlides[10] = loadImage("prez-images/Presentation.010.jpg"); - // presentationSlides[11] = loadImage("prez-images/Presentation.011.jpg"); - // presentationSlides[12] = loadImage("prez-images/Presentation.012.jpg"); - // presentationSlides[13] = loadImage("prez-images/Presentation.013.jpg"); - // presentationSlides[14] = loadImage("prez-images/Presentation.014.jpg"); - // presentationSlides[15] = loadImage("prez-images/Presentation.015.jpg"); - // slideCount = 4; - //println("DONE loading images!"); - } - - public void slideForward() { - if(currentSlide < slideCount - 1 && drawPresentation && !lockSlides){ - println("Slide Forward!"); - currentSlide++; - } else{ - println("No more slides. Can't go forward..."); - } - } - - public void slideBack() { - if(currentSlide > 0 && drawPresentation && !lockSlides){ - println("Slide Back!"); - currentSlide--; - } else { - println("On the first slide. Can't go back..."); - } - } - - public void draw() { - // ----- Drawing Presentation ------- - pushStyle(); - - image(presentationSlides[currentSlide], 0, 0, width, height); - - - if(lockSlides){ - //draw red rectangle to indicate that slides are locked - pushStyle(); - fill(255,0,0); - rect(width - 50, 25, 25, 25); - popStyle(); - } - - textFont(p3, 16); - fill(openbciBlue); - textAlign(CENTER); - text("Press [Enter] to exit presentation mode.", width/2, 31*(height/32)); - - popStyle(); - } -} diff --git a/OpenBCI_GUI/RadioConfig.pde b/OpenBCI_GUI/RadioConfig.pde new file mode 100644 index 000000000..ebac9c037 --- /dev/null +++ b/OpenBCI_GUI/RadioConfig.pde @@ -0,0 +1,375 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Radio_Config allows users to manually configure Cyton and Dongle +// +// Created: Colin Fausnaught, July 2016 +// +// Handles interactions between the radio system and OpenBCI systems +// using a direct Serial co +// +// Modified by Joel Murphy, January 2017 +// +// Modified by Richard Waltman, May 2020 +// +//////////////////////////////////////////////////////////////////////////////// + +class RadioConfig { + + private Serial serial_direct_board; + private final int NUM_RADIO_CHAN = 26; + private String rcStringReceived = ""; + private boolean autoscanPressed = false; + private boolean overridePressed = false; + + RadioConfig() { + + } + //=========== AUTOSCAN ============ + //= Scans through channels until a success message has been found + //= Used to align Cyton and Dongle on the same radio channel, in case there is a mismatch. + public void scan_channels(RadioConfigBox rcConfig){ + println("Radios_Config: scan_channels"); + autoscanPressed = true; + if(serial_direct_board == null){ + if(!connect_to_portName(rcConfig)){ + return; + } + } + for(int i = 1; i < NUM_RADIO_CHAN; i++){ + set_channel_over(rcConfig,i); + system_status(rcConfig); + if (board_message != null && board_message.toString().toLowerCase().contains("success")) { + return; + } + } + autoscanPressed = false; + closeSerialPort(); + } + + //=========== GET SYSTEM STATUS ============ + //= Get's the current status of the system + //= + //= First writes 0xF0 to let the board know + //= a command is coming, then writes the + //= command (0x07). + //= + //= After a short delay it then prints bytes + //= from the board. + //========================================== + + public void system_status(RadioConfigBox rcConfig){ + println("Radios_Config: system_status"); + rcStringReceived = ""; + serial_direct_board = null; + if(!connect_to_portName(rcConfig)){ + return; + } + serial_direct_board = new Serial(ourApplet, openBCI_portName, openBCI_baud); //force open the com port + if(serial_direct_board != null){ + serial_direct_board.write(0xF0); + serial_direct_board.write(0x07); + delay(50); + if(print_bytes(rcConfig)){ + String[] s = split(rcStringReceived, ':'); + if (s[0].equals("Success")) { + outputSuccess("Successfully connected to Cyton using " + openBCI_portName); + } else { + outputError("Failed to connect using " + openBCI_portName + ". Check hardware or try pressing 'Autoscan'."); + } + } + } else { + println("Error, no board connected"); + rcConfig.print_onscreen("No board connected!"); + } + closeSerialPort(); + } + + public boolean system_status(){ + println("Cyton AutoConnect Button: system_status"); + rcStringReceived = ""; + serial_direct_board = null; + if(!connect_to_portName()){ + return false; + } + serial_direct_board = new Serial(ourApplet, openBCI_portName, openBCI_baud); //force open the com port + if(serial_direct_board != null){ + serial_direct_board.write(0xF0); + serial_direct_board.write(0x07); + delay(50); + if(!print_bytes()){ + closeSerialPort(); + return false; + } else { + String[] s = split(rcStringReceived, ':'); + closeSerialPort(); + if (s[0].equals("Success")) { + outputSuccess("Successfully connected to Cyton using " + openBCI_portName); + return true; + } else { + outputError("Failed to connect using " + openBCI_portName + ". Check hardware or try pressing 'Autoscan'."); + return false; + } + } + } else { + println("Error, no board connected"); + return false; + } + } + + + + + + //============== GET CHANNEL =============== + //= Gets channel information from the radio. + //= + //= First writes 0xF0 to let the board know + //= a command is coming, then writes the + //= command (0x00). + //= + //= After a short delay it then prints bytes + //= from the board. + //========================================== + + public void get_channel(RadioConfigBox rcConfig){ + println("Radios_Config: get_channel"); + if(serial_direct_board == null){ + if(!connect_to_portName(rcConfig)){ + return; + } + } + serial_direct_board = new Serial(ourApplet, openBCI_portName, openBCI_baud); //force open the com port + if(serial_direct_board != null){ + serial_direct_board.write(0xF0); + serial_direct_board.write(0x00); + delay(100); + print_bytes(rcConfig); + } + else { + println("Error, no board connected"); + rcConfig.print_onscreen("No board connected!"); + } + closeSerialPort(); + } + + public boolean get_channel(){ + println("Cyton AutoConnect Button: get_channel"); + if(serial_direct_board == null){ + if(!connect_to_portName()){ + return false; + } + } + serial_direct_board = new Serial(ourApplet, openBCI_portName, openBCI_baud); //force open the com port + if(serial_direct_board != null){ + serial_direct_board.write(0xF0); + serial_direct_board.write(0x00); + delay(50); + if(!print_bytes()){ + closeSerialPort(); + return false; + } else { + String[] s = split(rcStringReceived, ':'); + closeSerialPort(); + if (s[0].equals("Success")) { + outputSuccess("Successfully connected to Cyton using " + openBCI_portName); + return true; + } else { + outputError("Failed to connect using " + openBCI_portName + ". Check hardware or try pressing 'Autoscan'."); + return false; + } + } + } else { + println("Error, no board connected"); + return false; + } + } + + //============== SET CHANNEL =============== + //= Sets the radio and board channel. + //= + //= First writes 0xF0 to let the board know + //= a command is coming, then writes the + //= command (0x01) followed by the number to + //= set the board and radio to. Channels can + //= only be 1-25. + //= + //= After a short delay it then prints bytes + //= from the board. + //========================================== + + public void set_channel(RadioConfigBox rcConfig, int channel_number){ + println("Radios_Config: set_channel"); + if(serial_direct_board == null){ + if(!connect_to_portName(rcConfig)){ + return; + } + } + serial_direct_board = new Serial(ourApplet, openBCI_portName, openBCI_baud); //force open the com port + if(serial_direct_board != null){ + if(channel_number > 0){ + serial_direct_board.write(0xF0); + serial_direct_board.write(0x01); + serial_direct_board.write(byte(channel_number)); + delay(1000); + print_bytes(rcConfig); + } + else rcConfig.print_onscreen("Please Select a Channel."); + } + else { + println("Error, no board connected"); + rcConfig.print_onscreen("No board connected!"); + } + closeSerialPort(); + } + + //========== SET CHANNEL OVERRIDE =========== + //= Sets the radio channel only + //= + //= First writes 0xF0 to let the board know + //= a command is coming, then writes the + //= command (0x02) followed by the number to + //= set the board and radio to. Channels can + //= only be 1-25. + //= + //= After a short delay it then prints bytes + //= from the board. + //========================================== + + public void set_channel_over(RadioConfigBox rcConfig, int channel_number){ + println("Radios_Config: set_ovr_channel"); + overridePressed = true; + if(serial_direct_board == null){ + if(!connect_to_portName(rcConfig)){ + return; + } + } + serial_direct_board = new Serial(ourApplet, openBCI_portName, openBCI_baud); //force open the com port + if(serial_direct_board != null){ + if(channel_number > 0){ + serial_direct_board.write(0xF0); + serial_direct_board.write(0x02); + serial_direct_board.write(byte(channel_number)); + delay(100); + print_bytes(rcConfig); + } + + else rcConfig.print_onscreen("Please Select a Channel."); + } + else { + println("Error, no board connected"); + rcConfig.print_onscreen("No board connected!"); + } + overridePressed = false; + closeSerialPort(); + } + + /**** Function to connect to a selected port ****/ // JAM 1/2017 + // Needs to be connected to something to perform the Radio_Config tasks + private boolean connect_to_portName(RadioConfigBox rcConfig){ + if(openBCI_portName != "N/A"){ + output("Attempting to open Serial/COM port: " + openBCI_portName); + try { + println("Radios_Config: connect_to_portName: Attempting to open serial port: " + openBCI_portName); + serial_output = new Serial(ourApplet, openBCI_portName, openBCI_baud); //open the com port + serial_output.clear(); // clear anything in the com port's buffer + // portIsOpen = true; + println("Radios_Config: connect_to_portName: Port is open!"); + serial_output.stop(); + return true; + } + catch (RuntimeException e){ + if (e.getMessage().contains("Port busy")) { + rcConfig.print_onscreen("Port Busy.\n\nTry a different port?"); + outputError("Radios_Config: Serial Port in use. Try another port or unplug/plug dongle."); + // portIsOpen = false; + } else { + println("Error connecting to selected Serial/COM port. Make sure your board is powered up and your dongle is plugged in."); + rcConfig.print_onscreen("Error connecting to Serial port.\n\nTry a different port?"); + } + closeSerialPort(); + println("Failed to connect using " + openBCI_portName); + return false; + } + } else { + output("No Serial/COM port selected. Please select your Serial/COM port and retry"); + rcConfig.print_onscreen("Select a Serial/COM port, then try again."); + return false; + } + } + + private boolean connect_to_portName(){ + if(openBCI_portName != "N/A"){ + output("Attempting to open Serial/COM port: " + openBCI_portName); + try { + println("Radios_Config: connect_to_portName: Attempting to open serial port: " + openBCI_portName); + serial_output = new Serial(ourApplet, openBCI_portName, openBCI_baud); //open the com port + serial_output.clear(); // clear anything in the com port's buffer + // portIsOpen = true; + println("Radios_Config: connect_to_portName: Port is open!"); + serial_output.stop(); + return true; + } + catch (RuntimeException e){ + if (e.getMessage().contains("Port busy")) { + serial_output = null; + outputError("Radios_Config: Serial Port in use. Try another port or unplug/plug dongle."); + // portIsOpen = false; + } else { + println("Error connecting to selected Serial/COM port. Make sure your board is powered up and your dongle is plugged in."); + } + closeSerialPort(); + println("Failed to connect using " + openBCI_portName); + return false; + } + } else { + output("No Serial/COM port selected. Please select your Serial/COM port and retry"); + return false; + } + } + + /**** Helper function to read from the serial ****/ + private boolean print_bytes(RadioConfigBox rc){ + if(board_message != null){ + println("Radios_Config: " + board_message.toString()); + rcStringReceived = board_message.toString(); + if(rcStringReceived.equals("Failure: System is Down")) { + rcStringReceived = "Cyton dongle could not connect to the board. Perhaps they are on different channels? \n\nTry pressing AUTOSCAN."; + } else if (rcStringReceived.equals("Success: System is Up")) { + rcStringReceived = "Success: Cyton and Dongle are paired. \n\nReady to Start Session!"; + } else if (!overridePressed && autoscanPressed && rcStringReceived.startsWith("Success: Host override")) { + rcStringReceived = "Please press AUTOSCAN one more time."; + } + rc.print_onscreen(rcStringReceived); + return true; + } else { + println("Radios_Config: Error reading from Serial/COM port"); + rc.print_onscreen("Error reading from Serial port.\n\nTry a different port?"); + return false; + } + } + + private boolean print_bytes(){ + if(board_message != null){ + println("Radios_Config: " + board_message.toString()); + rcStringReceived = board_message.toString(); + if(rcStringReceived.equals("Failure: System is Down")) { + rcStringReceived = "Cyton dongle could not connect to the board. Perhaps they are on different channels? Try pressing AUTOSCAN."; + } else if (rcStringReceived.equals("Success: System is Up")) { + rcStringReceived = "Success: Cyton and Dongle are paired. \n\nReady to Start Session!"; + } else if (rcStringReceived.startsWith("Success: Host override")) { + rcStringReceived = "Please press AUTOSCAN one more time."; + } + return true; + } else { + println("Radios_Config: Error reading from Serial/COM port"); + return false; + } + } + + public void closeSerialPort() { + if (serial_direct_board != null) { + serial_direct_board.stop(); + } + serial_direct_board = null; + } +} \ No newline at end of file diff --git a/OpenBCI_GUI/Radios_Config.pde b/OpenBCI_GUI/Radios_Config.pde deleted file mode 100644 index 06d23e984..000000000 --- a/OpenBCI_GUI/Radios_Config.pde +++ /dev/null @@ -1,334 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////// -// -// Radios_Config will be used for radio configuration -// integration. Also handles functions such as the "autconnect" -// feature. -// -// Created: Colin Fausnaught, July 2016 -// -// Handles interactions between the radio system and OpenBCI systems. -// It is important to note that this is using Serial communication directly -// rather than the Cyton class. I just found this easier to work -// with. -// -// Modified by Joel Murphy, January 2017 -// -//////////////////////////////////////////////////////////////////////////////// - -String rcStringReceived = ""; - -void autoconnect(){ - //Serial locBoard; //local serial instance just to make sure it's openbci, then connect to it if it is - String[] serialPorts = new String[Serial.list().length]; - String serialPort = ""; - serialPorts = Serial.list(); - - - for(int i = 0; i < serialPorts.length; i++){ - try{ - serialPort = serialPorts[i]; - board = new Serial(this,serialPort,115200); - println("try " + i + " " + serialPort + " at 115200 baud"); - output("Attempting to connect at 115200 baud to " + serialPort); // not working - delay(5000); - - board.write('v'); //modified by JAM 1/17 - delay(2000); - if(confirm_openbci()) { - println("Board connected on port " +serialPorts[i] + " with BAUD 115200"); - output("Connected to " + serialPort + "!"); - openBCI_portName = serialPorts[i]; - openBCI_baud = 115200; - board.stop(); - return; - } else { - println("Board not on port " + serialPorts[i] +" with BAUD 115200"); - board.stop(); - } - } - catch (Exception e){ - println("Exception " + serialPorts[i] + " " + e); - } - - try{ - board = new Serial(this,serialPort,230400); - println("try " + i + " " + serialPort + " at 230400 baud"); - output("Attempting to connect at 230400 baud to " + serialPort); // not working - delay(5000); - - board.write('v'); //modified by JAM 1/17 - delay(2000); - if(confirm_openbci()) { // was just confrim_openbci JAM 1/2017 - println("Board connected on port " +serialPorts[i] + " with BAUD 230400"); - output("Connected to " + serialPort + "!"); // not working - openBCI_baud = 230400; - openBCI_portName = serialPorts[i]; - board.stop(); - return; - } else { - println("Board not on port " + serialPorts[i] +" with BAUD 230400"); - board.stop(); - } - - } - catch (Exception e){ - println("Exception " + serialPorts[i] + " " + e); - } - } -} - -/**** Helper function for connection of boards ****/ -boolean confirm_openbci(){ - //println(board_message.toString()); - // if(board_message.toString().toLowerCase().contains("registers")) return true; - // print("board "); print(board_message.toString()); println("message"); - if(board_message != null){ - if(board_message.toString().toLowerCase().contains("ads")){ - return true; - } - } - return false; -} - -boolean confirm_openbci_v2(){ - //println(board_message.toString()); - if(board_message.toString().toLowerCase().contains("success")) return true; - // if(board_message.toString().contains("v2.")) return true; - else return false; -} -/**** Helper function for autoscan ****/ -boolean confirm_connected(){ - if( board_message != null && board_message.toString().toLowerCase().contains("success")) return true; // JAM added .containes("success") - else return false; -} - -/**** Helper function to read from the serial easily ****/ -boolean print_bytes(RadioConfigBox rc){ - if(board_message != null){ - println("Radios_Config: " + board_message.toString()); - rcStringReceived = board_message.toString(); - if(rcStringReceived.equals("Failure: System is Down")) { - rcStringReceived = "Cyton dongle could not connect to the board. Perhaps they are on different channels? Try pressing AUTOSCAN."; - } else if (rcStringReceived.equals("Success: System is Up")) { - rcStringReceived = "Success: Cyton and Dongle are paired. \n\nReady to Start Session!"; - } else if (rcStringReceived.startsWith("Success: Host override")) { - rcStringReceived = "Please press AUTOSCAN one more time."; - } - rc.print_onscreen(rcStringReceived); - return true; - } else { - return false; - } -} - -void print_bytes_error(RadioConfigBox rcConfig){ - println("Radios_Config: Error reading from Serial/COM port"); - rcConfig.print_onscreen("Error reading from Serial port.\n\nTry a different port?"); - board = null; -} - -/**** Function to connect to a selected port ****/ // JAM 1/2017 -// Needs to be connected to something to perform the Radio_Config tasks -boolean connect_to_portName(RadioConfigBox rcConfig){ - if(openBCI_portName != "N/A"){ - output("Attempting to open Serial/COM port: " + openBCI_portName); - try { - println("Radios_Config: connect_to_portName: Attempting to open serial port: " + openBCI_portName); - serial_output = new Serial(this, openBCI_portName, openBCI_baud); //open the com port - serial_output.clear(); // clear anything in the com port's buffer - // portIsOpen = true; - println("Radios_Config: connect_to_portName: Port is open!"); - // changeState(HubState.COMINIT); - board = serial_output; - return true; - } - catch (RuntimeException e){ - if (e.getMessage().contains("")) { - serial_output = null; - println("Radios_Config: connect_to_portName: Port in use, trying again later..."); - // portIsOpen = false; - } else { - println("Error connecting to selected Serial/COM port. Make sure your board is powered up and your dongle is plugged in."); - rcConfig.print_onscreen("Error connecting to Serial port.\n\nTry a different port?"); - } - board = null; - println("Failed to connect using " + openBCI_portName); - return false; - } - } else { - output("No Serial/COM port selected. Please select your Serial/COM port and retry"); - rcConfig.print_onscreen("Select a Serial/COM port, then try again."); - return false; - } -} - - - -//=========== GET SYSTEM STATUS ============ -//= Get's the current status of the system -//= -//= First writes 0xF0 to let the board know -//= a command is coming, then writes the -//= command (0x07). -//= -//= After a short delay it then prints bytes -//= from the board. -//========================================== - -void system_status(RadioConfigBox rcConfig){ - println("Radios_Config: system_status"); - - if(board == null){ - if(!connect_to_portName(rcConfig)){ - return; - } - } - if(board != null){ - board.write(0xF0); - board.write(0x07); - delay(100); - if(!print_bytes(rcConfig)){ - print_bytes_error(rcConfig); - } else { - String[] s = split(rcStringReceived, ':'); - if (s[0].equals("Success")) { - print_bytes(rcConfig); - outputSuccess("Successfully connected to Cyton using " + openBCI_portName); - } else { - outputError("Failed to connect using " + openBCI_portName + ". Check hardware or try pressing 'Autoscan'."); - } - } - } else { - println("Error, no board connected"); - rcConfig.print_onscreen("No board connected!"); - } -} - -//Scans through channels until a success message has been found -void scan_channels(RadioConfigBox rcConfig){ - println("Radios_Config: scan_channels"); - if(board == null){ - if(!connect_to_portName(rcConfig)){ - return; - } - } - for(int i = 1; i < 26; i++){ - - set_channel_over(rcConfig,i); - system_status(rcConfig); - if(confirm_connected()) return; // break; - } -} - - - -//============== GET CHANNEL =============== -//= Gets channel information from the radio. -//= -//= First writes 0xF0 to let the board know -//= a command is coming, then writes the -//= command (0x00). -//= -//= After a short delay it then prints bytes -//= from the board. -//========================================== - -void get_channel(RadioConfigBox rcConfig){ - println("Radios_Config: get_channel"); - if(board == null){ - if(!connect_to_portName(rcConfig)){ - return; - } - } - - if(board != null){ - board.write(0xF0); - board.write(0x00); - delay(100); - if(!print_bytes(rcConfig)){ - print_bytes_error(rcConfig); - } - } - else { - println("Error, no board connected"); - rcConfig.print_onscreen("No board connected!"); - } - } - -//============== SET CHANNEL =============== -//= Sets the radio and board channel. -//= -//= First writes 0xF0 to let the board know -//= a command is coming, then writes the -//= command (0x01) followed by the number to -//= set the board and radio to. Channels can -//= only be 1-25. -//= -//= After a short delay it then prints bytes -//= from the board. -//========================================== - -void set_channel(RadioConfigBox rcConfig, int channel_number){ - println("Radios_Config: set_channel"); - if(board == null){ - if(!connect_to_portName(rcConfig)){ - return; - } - } - if(board != null){ - if(channel_number > 0){ - board.write(0xF0); - board.write(0x01); - board.write(byte(channel_number)); - delay(1000); - if(!print_bytes(rcConfig)){ - print_bytes_error(rcConfig); - } - } - else rcConfig.print_onscreen("Please Select a Channel."); - } - else { - println("Error, no board connected"); - rcConfig.print_onscreen("No board connected!"); - } -} - -//========== SET CHANNEL OVERRIDE =========== -//= Sets the radio channel only -//= -//= First writes 0xF0 to let the board know -//= a command is coming, then writes the -//= command (0x02) followed by the number to -//= set the board and radio to. Channels can -//= only be 1-25. -//= -//= After a short delay it then prints bytes -//= from the board. -//========================================== - -void set_channel_over(RadioConfigBox rcConfig, int channel_number){ - println("Radios_Config: set_ovr_channel"); - if(board == null){ - if(!connect_to_portName(rcConfig)){ - return; - } - } - if(board != null){ - if(channel_number > 0){ - board.write(0xF0); - board.write(0x02); - board.write(byte(channel_number)); - delay(100); - if(!print_bytes(rcConfig)){ - print_bytes_error(rcConfig); - } - } - - else rcConfig.print_onscreen("Please Select a Channel."); - } - - else { - println("Error, no board connected"); - rcConfig.print_onscreen("No board connected!"); - } -} diff --git a/OpenBCI_GUI/SoftwareSettings.pde b/OpenBCI_GUI/SessionSettings.pde similarity index 60% rename from OpenBCI_GUI/SoftwareSettings.pde rename to OpenBCI_GUI/SessionSettings.pde index b26333a19..b4ea32b06 100644 --- a/OpenBCI_GUI/SoftwareSettings.pde +++ b/OpenBCI_GUI/SessionSettings.pde @@ -1,11 +1,11 @@ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /* -// This sketch saves and loads the following User Settings: +// This sketch saves and loads User Settings that appear during Sessions. // -- All Time Series widget settings in Live, Playback, and Synthetic modes // -- All FFT widget settings // -- Default Layout, Notch, Bandpass Filter, Framerate, Board Mode, and other Global Settings // -- Networking Mode and All settings for active networking protocol -// -- Accelerometer, Analog Read, Head Plot, EMG, Focus, Band Power, SSVEP, and Spectrogram +// -- Accelerometer, Analog Read, Head Plot, EMG, Band Power, and Spectrogram // -- Widget/Container Pairs // -- OpenBCI Data Format Settings (.txt and .csv) // Created: Richard Waltman - May/June 2018 @@ -18,7 +18,7 @@ // - in Interactivty.pde with the rest of the keyboard shortcuts // - in TopNav.pde when "Config" --> "Save Settings" || "Load Settings" is clicked // -- This allows User to store snapshots of most GUI settings in Users/.../Documents/OpenBCI_GUI/Settings/ -// -- After loading, only a few actions are required: start/stop the data stream and networking streams, open/close serial port, turn on/off Analog Read +// -- After loading, only a few actions are required: start/stop the data stream and networking streams, open/close serial port // // Tips on adding a new setting: // -- figure out if the setting is Global, in an existing widget, or in a new class or widget @@ -35,19 +35,17 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////// -// SoftwareSettings Class // +// SessionSettings Class // ///////////////////////////////// -class SoftwareSettings { +class SessionSettings { //Current version to save to JSON - String settingsVersion = "1.0.5"; + String settingsVersion = "3.0.0"; //impose minimum gui width and height in openBCI_GUI.pde int minGUIWidth = 705; int minGUIHeight = 400; //for screen resizing boolean screenHasBeenResized = false; float timeOfLastScreenResize = 0; - float timeOfGUIreinitialize = 0; - int reinitializeGUIdelay = 125; int widthOfLastScreen = 0; int heightOfLastScreen = 0; //default layout variables @@ -59,11 +57,9 @@ class SoftwareSettings { public final String[] fileDurations = {"5 Minutes", "15 minutes", "30 Minutes", "60 Minutes", "120 Minutes", "No Limit"}; public final int[] fileDurationInts = {5, 15, 30, 60, 120, -1}; public final int defaultOBCIMaxFileSize = 3; //4th option from the above list - public int cytonOBCIMaxFileSize = defaultOBCIMaxFileSize; - public int ganglionOBCIMaxFileSize = defaultOBCIMaxFileSize; private boolean logFileIsOpen = false; private long logFileStartTime; - private long logFileMaxDuration; + private long logFileMaxDurationNano = -1; //this is a global CColor that determines the style of all widget dropdowns ... this should go in WidgetManager.pde CColor dropdownColors = new CColor(); ///These `Save` vars are set to default when each widget instantiates @@ -74,7 +70,6 @@ class SoftwareSettings { //Time Series settings int tsVertScaleSave; int tsHorizScaleSave; - int checkForSuccessTS = 0; //Accelerometer settings int accVertScaleSave; int accHorizScaleSave; @@ -97,9 +92,6 @@ class SoftwareSettings { int emguVLimSave; int emgCreepSave; int emgMinDeltauVSave; - //Focus widget settings - int focusThemeSave; - int focusKeySave; //default data types for streams 1-4 in Networking widget int nwDataType1; int nwDataType2; @@ -107,27 +99,20 @@ class SoftwareSettings { int nwDataType4; String nwSerialPort; int nwProtocolSave; - //SSVEP Widget settings - int[] freqsSave; - boolean[] channelActivitySave; - int numSSVEPs; //Used to check if a playback file has data - int minNumRowsPlaybackFile = int(getSampleRateSafe()); + int minNumRowsPlaybackFile = int(currentBoard.getSampleRate()); //Spectrogram Widget settings int spectMaxFrqSave; int spectSampleRateSave; int spectLogLinSave; //default configuration settings file location and file name variables - public final String guiDataPath = System.getProperty("user.home")+File.separator+"Documents"+File.separator+"OpenBCI_GUI"+File.separator; - public final String recordingsPath = guiDataPath+"Recordings"+File.separator; - public final String settingsPath = guiDataPath+"Settings"+File.separator; - public final String consoleDataPath = guiDataPath+"Console_Data"+File.separator; private String sessionPath = ""; final String[] userSettingsFiles = { "CytonUserSettings.json", "DaisyUserSettings.json", "GanglionUserSettings.json", + "NovaXRUserSettings.json", "PlaybackUserSettings.json", "SynthFourUserSettings.json", "SynthEightUserSettings.json", @@ -137,6 +122,7 @@ class SoftwareSettings { "CytonDefaultSettings.json", "DaisyDefaultSettings.json", "GanglionDefaultSettings.json", + "NovaXRDefaultSettings.json", "PlaybackDefaultSettings.json", "SynthFourDefaultSettings.json", "SynthEightDefaultSettings.json", @@ -172,7 +158,7 @@ class SoftwareSettings { //Used to set text in dropdown menus when loading Networking settings String[] nwProtocolArray = {"Serial", "LSL", "UDP", "OSC"}; - String[] nwDataTypesArray = {"None", "TimeSeries", "FFT", "EMG", "BandPower", "Accel/Aux", "Focus", "Pulse", "SSVEP"}; + String[] nwDataTypesArray = {"None", "TimeSeries", "FFT", "EMG", "BandPower", "Accel/Aux", "Pulse"}; String[] nwBaudRatesArray = {"57600", "115200", "250000", "500000"}; //Used to set text in dropdown menus when loading Analog Read settings @@ -191,27 +177,14 @@ class SoftwareSettings { String[] emgCreepArray = {"0.9", "0.95", "0.98", "0.99", "0.999"}; String[] emgMinDeltauVArray = {"10 uV", "20 uV", "40 uV", "80 uV"}; - //Used to set text in dropdown menus when loading Focus Setings - String[] focusThemeArray = {"Green", "Orange", "Cyan"}; - String[] focusKeyArray = {"OFF", "UP", "SPACE"}; - //Used to set text in dropdown menus when loading Spectrogram Setings String[] spectMaxFrqArray = {"20 Hz", "40 Hz", "60 Hz", "100 Hz", "120 Hz", "250 Hz"}; String[] spectSampleRateArray = {"1 Hz", "5 hz", "10 Hz", "20 Hz", "40 Hz"}; - //Save Time Series settings variables - int tsActiveSetting = 1; - int tsGainSetting; - int tsInputTypeSetting; - int tsBiasSetting; - int tsSrb2Setting; - int tsSrb1Setting; - //Load global settings variables int loadLayoutSetting; int loadNotchSetting; int loadBandpassSetting; - BoardMode loadBoardMode; //Load TS dropdown variables int loadTimeSeriesVertScale; @@ -244,14 +217,6 @@ class SoftwareSettings { int emgCreepLoad; int emgMinDeltauVLoad; - //Focus widget settings - int focusThemeLoad; - int focusKeyLoad; - - //SSVEP widget settings - int numSSVEPsLoad; - int[] ssvepFreqsLoad = new int[4]; - List loadSSVEPActiveChans = new ArrayList(); //Band Power widget settings //smoothing and filter dropdowns are linked to FFT, so no need to save again @@ -270,19 +235,19 @@ class SoftwareSettings { String nwOscIp1Load; String nwOscIp2Load; String nwOscIp3Load; String nwOscIp4Load; String nwOscPort1Load; String nwOscPort2Load; String nwOscPort3Load; String nwOscPort4Load; String nwOscAddress1Load; String nwOscAddress2Load; String nwOscAddress3Load; String nwOscAddress4Load; - int nwOscFilter1Load; int nwOscFilter2Load; int nwOscFilter3Load; int nwOscFilter4Load; + boolean nwOscFilter1Load, nwOscFilter2Load, nwOscFilter3Load, nwOscFilter4Load; //UDP load variables String nwUdpIp1Load; String nwUdpIp2Load; String nwUdpIp3Load; String nwUdpPort1Load; String nwUdpPort2Load; String nwUdpPort3Load; - int nwUdpFilter1Load; int nwUdpFilter2Load; int nwUdpFilter3Load; + boolean nwUdpFilter1Load, nwUdpFilter2Load, nwUdpFilter3Load; //LSL load variables String nwLSLName1Load; String nwLSLName2Load; String nwLSLName3Load; String nwLSLType1Load; String nwLSLType2Load; String nwLSLType3Load; - String nwLSLNumChan1Load; String nwLSLNumChan2Load; String nwLSLNumChan3Load; - int nwLSLFilter1Load; int nwLSLFilter2Load; int nwLSLFilter3Load; + boolean nwLSLFilter1Load, nwLSLFilter2Load, nwLSLFilter3Load; //Serial load variables int nwSerialBaudRateLoad; - int nwSerialFilter1Load; + boolean nwSerialFilter1Load; + //Primary JSON objects for saving and loading data private JSONObject saveSettingsJSONData; private JSONObject loadSettingsJSONData; @@ -295,14 +260,12 @@ class SoftwareSettings { private final String kJSONKeyNetworking = "networking"; private final String kJSONKeyHeadplot = "headplot"; private final String kJSONKeyEMG = "emg"; - private final String kJSONKeyFocus = "focus"; private final String kJSONKeyBandPower = "bandPower"; - private final String kJSONKeySSVEP = "ssvep"; private final String kJSONKeyWidget = "widget"; private final String kJSONKeyVersion = "version"; private final String kJSONKeySpectrogram = "spectrogram"; - //used only in this tab to count the number of channels being used while saving/loading, this gets updated in updateToNChan whenever the number of channels being used changes + //used only in this class to count the number of channels being used while saving/loading, this gets updated in updateToNChan whenever the number of channels being used changes int slnchan; int numChanloaded; boolean chanNumError = false; @@ -321,10 +284,9 @@ class SoftwareSettings { int loadErrorTimerStart; int loadErrorTimeWindow = 5000; //Time window in milliseconds to apply channel settings to Cyton board. This is to avoid a GUI crash at ~ 4500-5000 milliseconds. Boolean loadErrorCytonEvent = false; - Boolean settingsLoaded = false; //Used to determine if settings are done loading successfully after init final int initTimeoutThreshold = 12000; //Timeout threshold in milliseconds - SoftwareSettings() { + SessionSettings() { //Instantiated on app start in OpenBCI_GUI.pde dropdownColors.setActive((int)color(150, 170, 200)); //bg color of box when pressed dropdownColors.setForeground((int)color(177, 184, 193)); //when hovering over any box (primary or dropdown) @@ -351,28 +313,17 @@ class SoftwareSettings { verbosePrint("Settings: LogFileStartTime = " + _time); } - public void setLogFileMaxDuration() { - int _maxFileSize = (eegDataSource == DATASOURCE_CYTON) ? cytonOBCIMaxFileSize : ganglionOBCIMaxFileSize; - logFileMaxDuration = fileDurationInts[_maxFileSize] * 1000000000L * 60; - println("Settings: LogFileMaxDuration = " + fileDurationInts[_maxFileSize] + " minutes"); + public void setLogFileDurationChoice(int choice) { + logFileMaxDurationNano = fileDurationInts[choice] * 1000000000L * 60; + println("Settings: LogFileMaxDuration = " + fileDurationInts[choice] + " minutes"); } //Only called during live mode && using OpenBCI Data Format public boolean maxLogTimeReached() { - if (logFileMaxDuration < 0) { + if (logFileMaxDurationNano < 0) { return false; } else { - return (System.nanoTime() - logFileStartTime) > (logFileMaxDuration); - } - } - - //Called in OpenBCI_GUI.pde to gate the above function - public boolean limitOBCILogFileDuration() { - if (logFileMaxDuration > 0) { - return true; - } else { - //If the value is less than zero, don't call maxLogTimeReached() - return false; + return (System.nanoTime() - logFileStartTime) > (logFileMaxDurationNano); } } @@ -401,27 +352,8 @@ class SoftwareSettings { try { this.save(defaultSettingsFileToSave); //to avoid confusion with save() image } catch (Exception e) { - println("InitSettings: Error trying to save settings"); - } - - //Try Auto-load GUI settings between checkpoints 4 and 5 during system init. - //Otherwise, load default settings. - String settingsFileToLoad = getPath("User", eegDataSource, nchan); - try { - this.load(settingsFileToLoad); - errorUserSettingsNotFound = false; - } catch (Exception e) { - //e.printStackTrace(); - println("InitSettings: " + settingsFileToLoad + " not found or other error."); - errorUserSettingsNotFound = true; - File f = new File(settingsFileToLoad); - //Only delete the settings file for other errors - //Leave file if it's just a channelNumber error or DataSource mismatch - if (!chanNumError && !dataSourceError) { - if (f.exists()) { - if (f.delete()) println("SoftwareSettings: Removed old/broken settings file."); - } - } + outputError("Failed to save Default Settings during Init. Please submit an Issue on GitHub."); + e.printStackTrace(); } } @@ -444,71 +376,8 @@ class SoftwareSettings { JSONObject saveTSSettings = new JSONObject(); saveTSSettings.setInt("Time Series Vert Scale", tsVertScaleSave); saveTSSettings.setInt("Time Series Horiz Scale", tsHorizScaleSave); - //////////////////////////////////////////////////////////////////////////////////// - // Case for saving TS settings in Cyton Data Modes // - if (eegDataSource == DATASOURCE_CYTON) { - //Set up an array to store channel settings - JSONArray saveTSSettingsJSONArray = new JSONArray(); - //Save all of the channel settings for number of Time Series channels being used - for (int i = 0; i < slnchan; i++) { - //Make a JSON Object for each of the Time Series Channels - JSONObject saveChannelSettings = new JSONObject(); - //Copy channel settings from channelSettingValues - for (int j = 0; j < numSettingsPerChannel; j++) { - switch(j) { //what setting are we looking at - case 0: //on/off - tsActiveSetting = Character.getNumericValue(channelSettingValues[i][j]); //Store integer value for active channel (0 or 1) from char array channelSettingValues - break; - case 1: //GAIN - tsGainSetting = Character.getNumericValue(channelSettingValues[i][j]); //Store integer value for gain - break; - case 2: //input type - tsInputTypeSetting = Character.getNumericValue(channelSettingValues[i][j]); //Store integer value for input type - break; - case 3: //BIAS - tsBiasSetting = Character.getNumericValue(channelSettingValues[i][j]); //Store integer value for bias - break; - case 4: // SRB2 - tsSrb2Setting = Character.getNumericValue(channelSettingValues[i][j]); //Store integer value for srb2 - break; - case 5: // SRB1 - tsSrb1Setting = Character.getNumericValue(channelSettingValues[i][j]); //Store integer value for srb1 - break; - } - //Store all channel settings in Time Series JSON object, one channel at a time - saveChannelSettings.setInt("Channel_Number", (i+1)); - saveChannelSettings.setInt("Active", tsActiveSetting); - saveChannelSettings.setInt("PGA Gain", int(tsGainSetting)); - saveChannelSettings.setInt("Input Type", tsInputTypeSetting); - saveChannelSettings.setInt("Bias", tsBiasSetting); - saveChannelSettings.setInt("SRB2", tsSrb2Setting); - saveChannelSettings.setInt("SRB1", tsSrb1Setting); - saveTSSettingsJSONArray.setJSONObject(i, saveChannelSettings); - } //end channel settings for loop - } //end all channels for loop - saveTSSettings.setJSONArray("channelSettings", saveTSSettingsJSONArray); //Set the JSON array for all channels - } - ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Case for saving TS settings when in Ganglion, Synthetic, and Playback data modes // - if (eegDataSource == DATASOURCE_PLAYBACKFILE || eegDataSource == DATASOURCE_SYNTHETIC || eegDataSource == DATASOURCE_GANGLION) { - //Set up an array to store channel settings - JSONArray saveTSSettingsJSONArray = new JSONArray(); - for (int i = 0; i < slnchan; i++) { //For all channels... - //Make a JSON Object for each of the Time Series Channels - JSONObject saveTimeSeriesSettings = new JSONObject(); - //Get integer value from char array channelSettingValues - tsActiveSetting = Character.getNumericValue(channelSettingValues[i][0]); - //Catch case where channel settings is not 0 or 1, due to unkown error - tsActiveSetting = (tsActiveSetting == 0 || tsActiveSetting == 1) ? - tsActiveSetting ^= 1 : - 1; //save channel setting as active if there is an error - saveTimeSeriesSettings.setInt("Channel_Number", (i+1)); - saveTimeSeriesSettings.setInt("Active", tsActiveSetting); - saveTSSettingsJSONArray.setJSONObject(i, saveTimeSeriesSettings); - } //end loop for all channels - saveTSSettings.setJSONArray("channelSettings", saveTSSettingsJSONArray); //Set the JSON array for all channels - } saveSettingsJSONData.setJSONObject(kJSONKeyTimeSeries, saveTSSettings); + //Make a second JSON object within our JSONArray to store Global settings for the GUI JSONObject saveGlobalSettings = new JSONObject(); saveGlobalSettings.setBoolean("Expert Mode", expertModeToggle); @@ -516,16 +385,8 @@ class SoftwareSettings { saveGlobalSettings.setInt("Notch", dataProcessingNotchSave); saveGlobalSettings.setInt("Bandpass Filter", dataProcessingBandpassSave); saveGlobalSettings.setInt("Framerate", frameRateCounter); - saveGlobalSettings.setBoolean("Accelerometer Mode", w_accelerometer.isAccelModeActive()); - if (eegDataSource == DATASOURCE_CYTON) { //Only save these settings if you are using a Cyton board for live streaming - saveGlobalSettings.setInt("Analog Read Vert Scale", arVertScaleSave); - saveGlobalSettings.setInt("Analog Read Horiz Scale", arHorizScaleSave); - saveGlobalSettings.setBoolean("Pulse Analog Read", w_pulsesensor.analogReadOn); - saveGlobalSettings.setBoolean("Analog Read", w_analogRead.analogReadOn); - saveGlobalSettings.setBoolean("Digital Read", w_digitalRead.digitalReadOn); - saveGlobalSettings.setBoolean("Marker Mode", w_markermode.markerModeOn); - saveGlobalSettings.setInt("Board Mode", cyton.curBoardMode.ordinal()); - } + saveGlobalSettings.setInt("Analog Read Vert Scale", arVertScaleSave); + saveGlobalSettings.setInt("Analog Read Horiz Scale", arHorizScaleSave); saveSettingsJSONData.setJSONObject(kJSONKeySettings, saveGlobalSettings); /////Setup JSON Object for gui version and settings Version @@ -569,7 +430,7 @@ class SoftwareSettings { saveNetworkingSettings.setString("OSC_ip"+i, (String) w_networking.getCP5Map().get("OSC_ip"+i)); saveNetworkingSettings.setString("OSC_port"+i, (String) w_networking.getCP5Map().get("OSC_port"+i)); saveNetworkingSettings.setString("OSC_address"+i, (String) w_networking.getCP5Map().get("OSC_address"+i)); - saveNetworkingSettings.setInt("OSC_filter"+i, (Integer) w_networking.getCP5Map().get("filter"+i)); + saveNetworkingSettings.setBoolean("OSC_filter"+i, (boolean) w_networking.getCP5Map().get("filter"+i)); } break; case 2: @@ -577,7 +438,7 @@ class SoftwareSettings { saveNetworkingSettings.setInt("UDP_DataType"+i, (Integer) w_networking.getCP5Map().get(w_networking.datatypeNames[i-1])); saveNetworkingSettings.setString("UDP_ip"+i, (String) w_networking.getCP5Map().get("UDP_ip"+i)); saveNetworkingSettings.setString("UDP_port"+i, (String) w_networking.getCP5Map().get("UDP_port"+i)); - saveNetworkingSettings.setInt("UDP_filter"+i, (Integer) w_networking.getCP5Map().get("filter"+i)); + saveNetworkingSettings.setBoolean("UDP_filter"+i, (boolean) w_networking.getCP5Map().get("filter"+i)); } break; case 1: @@ -585,14 +446,13 @@ class SoftwareSettings { saveNetworkingSettings.setInt("LSL_DataType"+i, (Integer) w_networking.getCP5Map().get(w_networking.datatypeNames[i-1])); saveNetworkingSettings.setString("LSL_name"+i, (String) w_networking.getCP5Map().get("LSL_name"+i)); saveNetworkingSettings.setString("LSL_type"+i, (String) w_networking.getCP5Map().get("LSL_type"+i)); - saveNetworkingSettings.setString("LSL_numchan"+i, (String) w_networking.getCP5Map().get("LSL_numchan"+i)); - saveNetworkingSettings.setInt("LSL_filter"+i, (Integer) w_networking.getCP5Map().get("filter"+i)); + saveNetworkingSettings.setBoolean("LSL_filter"+i, (boolean) w_networking.getCP5Map().get("filter"+i)); } break; case 0: saveNetworkingSettings.setInt("Serial_DataType1", (Integer) w_networking.getCP5Map().get("dataType1")); saveNetworkingSettings.setInt("Serial_baudrate", (Integer) w_networking.getCP5Map().get("baud_rate")); - saveNetworkingSettings.setInt("Serial_filter1", (Integer) w_networking.getCP5Map().get("filter1")); + saveNetworkingSettings.setBoolean("Serial_filter1", (boolean) w_networking.getCP5Map().get("filter1")); saveNetworkingSettings.setString("Serial_portName", (String) w_networking.getCP5Map().get("port_name")); break; }//end of networking proctocol switch @@ -627,37 +487,6 @@ class SoftwareSettings { //Set the EMG JSON Object saveSettingsJSONData.setJSONObject(kJSONKeyEMG, saveEMGSettings); - ///////////////////////////////////////////////Setup new JSON object to save Focus settings - JSONObject saveFocusSettings = new JSONObject(); - - //Save Focus theme - saveFocusSettings.setInt("Focus_theme", focusThemeSave); - //Save Focus keypress - saveFocusSettings.setInt("Focus_keypress", focusKeySave); - //Set the Focus JSON Object - saveSettingsJSONData.setJSONObject(kJSONKeyFocus, saveFocusSettings); - - ///////////////////////////////////////////////Setup new JSON object to save SSVEP settings - JSONObject saveSSVEPSettings = new JSONObject(); - - int num_ssveps = numSSVEPs + 1; //add 1 here, dropdown items start count from 0 - saveSSVEPSettings.setInt("NumSSVEPs", num_ssveps); - //Save data from the Active channel checkBoxes - JSONArray saveActiveChanSSVEP = new JSONArray(); - int numActiveSSVEPChan = w_ssvep.numActiveChannels; - for (int i = 0; i < numActiveSSVEPChan; i++) { - int activeChan = w_ssvep.ssvepChanSelect.activeChan.get(i) + 1; //add 1 here so channel numbers are correct - saveActiveChanSSVEP.setInt(i, activeChan); - } - saveSSVEPSettings.setJSONArray("activeChannels", saveActiveChanSSVEP); - //Save data from the 1 to 4 ssvep frequency dropdowns inside the widget - JSONObject ssvepFrequencies = new JSONObject(); - for (int i = 0; i < num_ssveps; i++) { - ssvepFrequencies.setInt("SSVEP_"+i, w_ssvep.freqs[i]); - } - saveSSVEPSettings.setJSONObject("SSVEP_frequencies", ssvepFrequencies); - saveSettingsJSONData.setJSONObject(kJSONKeySSVEP, saveSSVEPSettings); - ///////////////////////////////////////////////Setup new JSON object to save Band Power settings JSONObject saveBPSettings = new JSONObject(); @@ -713,7 +542,7 @@ class SoftwareSettings { //println("widget"+i+" is not active"); } } - println("SoftwareSettings: " + numActiveWidgets + " active widgets saved!"); + println("SessionSettings: " + numActiveWidgets + " active widgets saved!"); //Print what widgets are in the containers used by current layout for only the number of active widgets //for (int i = 0; i < numActiveWidgets; i++) { //int containerCounter = wm.layouts.get(currentLayout-1).containerInts[i]; @@ -736,6 +565,8 @@ class SoftwareSettings { //Load all saved User Settings from a JSON file if it exists loadSettingsJSONData = loadJSONObject(loadGUISettingsFileLocation); + verbosePrint(loadSettingsJSONData.toString()); + //Check the number of channels saved to json first! JSONObject loadDataSettings = loadSettingsJSONData.getJSONObject(kJSONKeyDataInfo); numChanloaded = loadDataSettings.getInt("Channels"); @@ -766,34 +597,12 @@ class SoftwareSettings { loadBandpassSetting = loadGlobalSettings.getInt("Bandpass Filter"); loadFramerate = loadGlobalSettings.getInt("Framerate"); Boolean loadExpertModeToggle = loadGlobalSettings.getBoolean("Expert Mode"); - Boolean loadAccelerometer = loadGlobalSettings.getBoolean("Accelerometer Mode"); - if (eegDataSource == DATASOURCE_CYTON) { //Only save these settings if you are using a Cyton board for live streaming - loadAnalogReadVertScale = loadGlobalSettings.getInt("Analog Read Vert Scale"); - loadAnalogReadHorizScale = loadGlobalSettings.getInt("Analog Read Horiz Scale"); - loadBoardMode = BoardMode.values()[loadGlobalSettings.getInt("Board Mode")]; - } + loadAnalogReadVertScale = loadGlobalSettings.getInt("Analog Read Vert Scale"); + loadAnalogReadHorizScale = loadGlobalSettings.getInt("Analog Read Horiz Scale"); //Store loaded layout to current layout variable currentLayout = loadLayoutSetting; //Load more global settings after this line, if needed - //Create a string array to print global settings to console - String[] loadedGlobalSettings = { - "Using Layout Number: " + loadLayoutSetting, - "Default Notch: " + loadNotchSetting, //default notch - "Default BP: " + loadBandpassSetting, //default bp - "Default Framerate: " + loadFramerate, //default framerate - "Expert Mode: " + loadExpertModeToggle, - "TS Vert Scale: " + loadTimeSeriesVertScale, - "TS Horiz Scale: " + loadTimeSeriesHorizScale, - "Analog Vert Scale: " + loadAnalogReadVertScale, - "Analog Horiz Scale: " + loadAnalogReadHorizScale, - "Accelerometer: " + loadAccelerometer, - "Board Mode: " + loadBoardMode, - //Add new global settings above this line to print to console - }; - //Print the global settings that have been loaded to the console - //printArray(loadedGlobalSettings); - //get the FFT settings JSONObject loadFFTSettings = loadSettingsJSONData.getJSONObject(kJSONKeyFFT); fftMaxFrqLoad = loadFFTSettings.getInt("FFT_Max Freq"); @@ -802,25 +611,10 @@ class SoftwareSettings { fftSmoothingLoad = loadFFTSettings.getInt("FFT_Smoothing"); fftFilterLoad = loadFFTSettings.getInt("FFT_Filter"); - //Create a string array to print to console - String[] loadedFFTSettings = { - "FFT_Max Frequency: " + fftMaxFrqLoad, - "FFT_Max uV: " + fftMaxuVLoad, - "FFT_Log/Lin: " + fftLogLinLoad, - "FFT_Smoothing: " + fftSmoothingLoad, - "FFT_Filter: " + fftFilterLoad - }; - //Print the FFT settings that have been loaded to the console - //printArray(loadedFFTSettings); - //get the Accelerometer settings JSONObject loadAccSettings = loadSettingsJSONData.getJSONObject(kJSONKeyAccel); loadAccelVertScale = loadAccSettings.getInt("Accelerometer Vert Scale"); loadAccelHorizScale = loadAccSettings.getInt("Accelerometer Horiz Scale"); - String[] loadedAccSettings = { - "Accelerometer Vert Scale: " + loadAccelVertScale, - "Accelerometer Horiz Scale: " + loadAccelHorizScale - }; //get the Networking Settings JSONObject loadNetworkingSettings = loadSettingsJSONData.getJSONObject(kJSONKeyNetworking); @@ -843,10 +637,10 @@ class SoftwareSettings { nwOscAddress2Load = loadNetworkingSettings.getString("OSC_address2"); nwOscAddress3Load = loadNetworkingSettings.getString("OSC_address3"); nwOscAddress4Load = loadNetworkingSettings.getString("OSC_address4"); - nwOscFilter1Load = loadNetworkingSettings.getInt("OSC_filter1"); - nwOscFilter2Load = loadNetworkingSettings.getInt("OSC_filter2"); - nwOscFilter3Load = loadNetworkingSettings.getInt("OSC_filter3"); - nwOscFilter4Load = loadNetworkingSettings.getInt("OSC_filter4"); + nwOscFilter1Load = loadNetworkingSettings.getBoolean("OSC_filter1"); + nwOscFilter2Load = loadNetworkingSettings.getBoolean("OSC_filter2"); + nwOscFilter3Load = loadNetworkingSettings.getBoolean("OSC_filter3"); + nwOscFilter4Load = loadNetworkingSettings.getBoolean("OSC_filter4"); break; case 2: nwDataType1 = loadNetworkingSettings.getInt("UDP_DataType1"); @@ -858,9 +652,9 @@ class SoftwareSettings { nwUdpPort1Load = loadNetworkingSettings.getString("UDP_port1"); nwUdpPort2Load = loadNetworkingSettings.getString("UDP_port2"); nwUdpPort3Load = loadNetworkingSettings.getString("UDP_port3"); - nwUdpFilter1Load = loadNetworkingSettings.getInt("UDP_filter1"); - nwUdpFilter2Load = loadNetworkingSettings.getInt("UDP_filter2"); - nwUdpFilter3Load = loadNetworkingSettings.getInt("UDP_filter3"); + nwUdpFilter1Load = loadNetworkingSettings.getBoolean("UDP_filter1"); + nwUdpFilter2Load = loadNetworkingSettings.getBoolean("UDP_filter2"); + nwUdpFilter3Load = loadNetworkingSettings.getBoolean("UDP_filter3"); break; case 1: nwDataType1 = loadNetworkingSettings.getInt("LSL_DataType1"); @@ -872,17 +666,14 @@ class SoftwareSettings { nwLSLType1Load = loadNetworkingSettings.getString("LSL_type1"); nwLSLType2Load = loadNetworkingSettings.getString("LSL_type2"); nwLSLType3Load = loadNetworkingSettings.getString("LSL_type3"); - nwLSLNumChan1Load = loadNetworkingSettings.getString("LSL_numchan1"); - nwLSLNumChan2Load = loadNetworkingSettings.getString("LSL_numchan2"); - nwLSLNumChan3Load = loadNetworkingSettings.getString("LSL_numchan3"); - nwLSLFilter1Load = loadNetworkingSettings.getInt("LSL_filter1"); - nwLSLFilter2Load = loadNetworkingSettings.getInt("LSL_filter2"); - nwLSLFilter3Load = loadNetworkingSettings.getInt("LSL_filter3"); + nwLSLFilter1Load = loadNetworkingSettings.getBoolean("LSL_filter1"); + nwLSLFilter2Load = loadNetworkingSettings.getBoolean("LSL_filter2"); + nwLSLFilter3Load = loadNetworkingSettings.getBoolean("LSL_filter3"); break; case 0: nwDataType1 = loadNetworkingSettings.getInt("Serial_DataType1"); nwSerialBaudRateLoad = loadNetworkingSettings.getInt("Serial_baudrate"); - nwSerialFilter1Load = loadNetworkingSettings.getInt("Serial_filter1"); + nwSerialFilter1Load = loadNetworkingSettings.getBoolean("Serial_filter1"); nwSerialPort = loadNetworkingSettings.getString("Serial_portName"); break; } //end switch case for all networking types @@ -894,16 +685,6 @@ class SoftwareSettings { hpContoursLoad = loadHeadplotSettings.getInt("HP_contours"); hpSmoothingLoad = loadHeadplotSettings.getInt("HP_smoothing"); - //Create a string array to print to console - String[] loadedHPSettings = { - "HP_intensity: " + hpIntensityLoad, - "HP_polarity: " + hpPolarityLoad, - "HP_contours: " + hpContoursLoad, - "HP_smoothing: " + hpSmoothingLoad - }; - //Print the Headplot settings - //printArray(loadedHPSettings); - //get the EMG settings JSONObject loadEMGSettings = loadSettingsJSONData.getJSONObject(kJSONKeyEMG); emgSmoothingLoad = loadEMGSettings.getInt("EMG_smoothing"); @@ -911,46 +692,6 @@ class SoftwareSettings { emgCreepLoad = loadEMGSettings.getInt("EMG_creepspeed"); emgMinDeltauVLoad = loadEMGSettings.getInt("EMG_minuV"); - //Create a string array to print to console - String[] loadedEMGSettings = { - "EMG_smoothing: " + emgSmoothingLoad, - "EMG_uVlimit: " + emguVLimLoad, - "EMG_creepspeed: " + emgCreepLoad, - "EMG_minuV: " + emgMinDeltauVLoad - }; - //Print the EMG settings - //printArray(loadedEMGSettings); - - //get the Focus settings - JSONObject loadFocusSettings = loadSettingsJSONData.getJSONObject(kJSONKeyFocus); - focusThemeLoad = loadFocusSettings.getInt("Focus_theme"); - focusKeyLoad = loadFocusSettings.getInt("Focus_keypress"); - - //Create a string array to print to console - String[] loadedFocusSettings = { - "Focus_theme: " + focusThemeLoad, - "Focus_keypress: " + focusKeyLoad - }; - //Print the EMG settings - //printArray(loadedFocusSettings); - - //Clear the list and array for holding SSVEP settings - loadSSVEPActiveChans.clear(); //clear this list so user can load settings over and over - ssvepFreqsLoad = new int[4]; //clear the dropdown settings array - //get the ssvep settings - JSONObject loadSSVEPSettings = loadSettingsJSONData.getJSONObject(kJSONKeySSVEP); - numSSVEPsLoad = loadSSVEPSettings.getInt("NumSSVEPs"); - JSONObject loadSSVEPFreqs = loadSSVEPSettings.getJSONObject("SSVEP_frequencies"); - for (int i = 0; i < numSSVEPsLoad; i++) { - int f = loadSSVEPFreqs.getInt("SSVEP_" + i); - ssvepFreqsLoad[i] = f; - } - JSONArray loadSSVEPChan = loadSSVEPSettings.getJSONArray("activeChannels"); - for (int i = 0; i < loadSSVEPChan.size(); i++) { - loadSSVEPActiveChans.add(loadSSVEPChan.getInt(i)); - } - //println("Settings: ssvep active chans loaded = " + loadSSVEPActiveChans); - //Get Band Power widget settings loadBPActiveChans.clear(); JSONObject loadBPSettings = loadSettingsJSONData.getJSONObject(kJSONKeyBandPower); @@ -1019,18 +760,6 @@ class SoftwareSettings { //}//end case for all objects in JSON - //Apply notch - dataProcessing.currentNotch_ind = loadNotchSetting; - topNav.filtNotchButton.but_txt = "Notch\n" + dataProcessingNotchArray[loadNotchSetting]; - //Apply Bandpass filter - dataProcessing.currentFilt_ind = loadBandpassSetting; - topNav.filtBPButton.but_txt = "BP Filt\n" + dataProcessingBPArray[loadBandpassSetting]; //this works - - //Apply Board Mode to Cyton Only - if (eegDataSource == DATASOURCE_CYTON) { - applyBoardMode(); - } - //Apply Expert Mode toggle if (loadExpertModeToggle) { topNav.configSelector.configOptions.get(0).setString("Turn Expert Mode Off"); @@ -1048,20 +777,16 @@ class SoftwareSettings { frameRateCounter = loadFramerate; switch (frameRateCounter) { case 0: - topNav.fpsButton.setString("24 fps"); - frameRate(24); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate + setFrameRate(24); break; case 1: - topNav.fpsButton.setString("30 fps"); - frameRate(30); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate + setFrameRate(30); break; case 2: - topNav.fpsButton.setString("45 fps"); - frameRate(45); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate + setFrameRate(45); break; case 3: - topNav.fpsButton.setString("60 fps"); - frameRate(60); //refresh rate ... this will slow automatically, if your processor can't handle the specified rate + setFrameRate(60); break; } @@ -1082,99 +807,10 @@ class SoftwareSettings { w_headPlot.headPlot.setPositionSize(w_headPlot.headPlot.hp_x, w_headPlot.headPlot.hp_y, w_headPlot.headPlot.hp_w, w_headPlot.headPlot.hp_h, w_headPlot.headPlot.hp_win_x, w_headPlot.headPlot.hp_win_y); println("Headplot is active: Redrawing"); } - - //Apply the accelerometer boolean to backend and frontend when using Ganglion. When using Cyton, applyBoardMode does the work. - if (eegDataSource == DATASOURCE_GANGLION) { - if (loadAccelerometer) { //if loadAccelerometer is true. This has been loaded from JSON file. - // daniellasry: it seems the ganglion board does not like turning on the accelerometer - // immediately after activating channels. From what I can tell, the issue is in the - // firmware. This delay is a workaround for the issue. - // retiutut: Containing this fix to BLED112 only! - if (ganglion.getInterface() == INTERFACE_HUB_BLED112) { - delay(1000); - } - ganglion.accelStart(); //send message to hub - } else { - ganglion.accelStop(); //send message to hub - } - } - } //end of loadGUISettings ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private void applyBoardMode() { - //Apply Board Mode - switch(loadBoardMode) { //Switch-case for loaded board mode - case DEFAULT: - cyton.setBoardMode(BoardMode.DEFAULT); - //outputSuccess("Starting to read accelerometer"); - w_analogRead.analogReadOn = false; - w_pulsesensor.analogReadOn = false; - w_digitalRead.digitalReadOn = false; - w_markermode.markerModeOn = false; - break; - case DEBUG: //Not being used currently - break; - case ANALOG: - if (cyton.isPortOpen()) { //This code has been copied from AnalogRead - if (cyton.getBoardMode() != BoardMode.ANALOG) { - cyton.setBoardMode(BoardMode.ANALOG); - if (cyton.isWifi()) { - output("Starting to read analog inputs on pin marked A5 (D11) and A6 (D12)"); - } else { - output("Starting to read analog inputs on pin marked A5 (D11), A6 (D12) and A7 (D13)"); - } - w_digitalRead.digitalReadOn = false; - w_markermode.markerModeOn = false; - w_pulsesensor.analogReadOn = true; - w_analogRead.analogReadOn = true; - } else { - cyton.setBoardMode(BoardMode.DEFAULT); - output("Starting to read accelerometer"); - } - } - break; - case DIGITAL: - if (cyton.isPortOpen()) { //This code has been copied from DigitalRead - if (cyton.getBoardMode() != BoardMode.DIGITAL) { - cyton.setBoardMode(BoardMode.DIGITAL); - if (cyton.isWifi()) { - output("Starting to read digital inputs on pin marked D11, D12 and D17"); - } else { - output("Starting to read digital inputs on pin marked D11, D12, D13, D17 and D18"); - } - w_analogRead.analogReadOn = false; - w_pulsesensor.analogReadOn = false; - w_markermode.markerModeOn = false; - } else { - cyton.setBoardMode(BoardMode.DEFAULT); - outputSuccess("Starting to read accelerometer"); - } - } - break; - case MARKER: - if ((cyton.isPortOpen() && eegDataSource == DATASOURCE_CYTON) || eegDataSource == DATASOURCE_SYNTHETIC) { - if (cyton.getBoardMode() != BoardMode.MARKER) { - cyton.setBoardMode(BoardMode.MARKER); - output("Starting to read markers"); - w_markermode.markerModeButton.setString("Turn Marker Off"); - w_analogRead.analogReadOn = false; - w_pulsesensor.analogReadOn = false; - w_digitalRead.digitalReadOn = false; - } else { - cyton.setBoardMode(BoardMode.DEFAULT); - output("Starting to read accelerometer"); - w_markermode.markerModeButton.setString("Turn Marker On"); - w_analogRead.analogReadOn = false; - w_pulsesensor.analogReadOn = false; - w_digitalRead.digitalReadOn = false; - } - } - break; - }//end switch/case - } - private void loadApplyWidgetDropdownText() { ////////Apply Time Series dropdown settings in loadApplyTimeSeriesSettings() instead of here @@ -1241,51 +877,9 @@ class SoftwareSettings { minUVRange(emgMinDeltauVLoad); w_emg.cp5_widget.getController("minUVRange").getCaptionLabel().setText(emgMinDeltauVArray[emgMinDeltauVLoad]); - ////////////////////////////Apply Focus settings - ChooseFocusColor(focusThemeLoad); - w_focus.cp5_widget.getController("ChooseFocusColor").getCaptionLabel().setText(focusThemeArray[focusThemeLoad]); - - StrokeKeyWhenFocused(focusKeyLoad); - w_focus.cp5_widget.getController("StrokeKeyWhenFocused").getCaptionLabel().setText(focusKeyArray[focusKeyLoad]); - - ////////////////////////////Apply SSVEP settings - //Apply number ssveps dropdown - NumberSSVEP(numSSVEPsLoad - 1); //subtract 1 here because dropdowns count from 0 - w_ssvep.cp5_widget.getController("NumberSSVEP").getCaptionLabel().setText(String.valueOf(numSSVEPsLoad)); - //Apply ssvep frequency dropdowns - for (int i = 0; i < numSSVEPsLoad; i++) { - if (ssvepFreqsLoad[i] > 1) { - w_ssvep.cp5_ssvep.getController("Frequency_"+(i+1)).getCaptionLabel().setText(ssvepFreqsLoad[i]+" Hz"); - w_ssvep.cp5_ssvep.get(ScrollableList.class, "Frequency_"+(i+1)).setValue(ssvepFreqsLoad[i] - 7); - w_ssvep.freqs[i] = ssvepFreqsLoad[i]; - } else { // -1 - none selected - w_ssvep.cp5_ssvep.getController("Frequency_"+(i+1)).getCaptionLabel().setText("Frequency_"+(i+1)); - } - } - //Apply ssvepActiveChans settings by activating/deactivating check boxes for all channels - try { - //deactivate all channels and then activate the active channels - w_ssvep.ssvepChanSelect.cp5_channelCheckboxes.get(CheckBox.class, "SSVEP_Channels").deactivateAll(); - if (loadSSVEPActiveChans.size() > 0) { - int activeChanCounter = 0; - for (int i = 0; i < nchan; i++) { - if (activeChanCounter < loadSSVEPActiveChans.size()) { - //subtract 1 because cp5 starts count from 0 - if (i == (loadSSVEPActiveChans.get(activeChanCounter) - 1)) { - w_ssvep.ssvepChanSelect.cp5_channelCheckboxes.get(CheckBox.class, "SSVEP_Channels").activate(i); - activeChanCounter++; - } - } - } - } - } catch (Exception e) { - println("Settings: Exception caught applying ssvep settings" + e); - } - verbosePrint("Settings: SSVEP Active Channels: " + loadSSVEPActiveChans); - ////////////////////////////Apply Band Power settings try { - //use the same process as ssvep to apply channel checkbox settings + //apply channel checkbox settings w_bandPower.bpChanSelect.cp5_channelCheckboxes.get(CheckBox.class, "BP_Channels").deactivateAll(); if (loadBPActiveChans.size() > 0) { int activeChanCounterBP = 0; @@ -1302,7 +896,7 @@ class SoftwareSettings { } catch (Exception e) { println("Settings: Exception caught applying band power settings " + e); } - verbosePrint("Settings: SSVEP Active Channels: " + loadSSVEPActiveChans); + verbosePrint("Settings: Band Power Active Channels: " + loadBPActiveChans); ////////////////////////////Apply Spectrogram settings //Apply Max Freq dropdown @@ -1313,46 +907,15 @@ class SoftwareSettings { SpectrogramLogLin(spectLogLinLoad); w_spectrogram.cp5_widget.getController("SpectrogramLogLin").getCaptionLabel().setText(fftLogLinArray[spectLogLinLoad]); - - //Apply ssvepActiveChans settings by activating/deactivating check boxes for all channels - try { - //deactivate all channels and then activate the active channels - w_spectrogram.spectChanSelectTop.cp5_channelCheckboxes.get(CheckBox.class, "Spectrogram_Channels_Top").deactivateAll(); - if (loadSpectActiveChanTop.size() > 0) { - int activeChanCounter = 0; - for (int i = 0; i < nchan; i++) { - if (activeChanCounter < loadSpectActiveChanTop.size()) { - //subtract 1 because cp5 starts count from 0 - if (i == (loadSpectActiveChanTop.get(activeChanCounter) - 1)) { - w_spectrogram.spectChanSelectTop.cp5_channelCheckboxes.get(CheckBox.class, "Spectrogram_Channels_Top").activate(i); - activeChanCounter++; - } - } - } - } - //deactivate all channels and then activate the active channels - w_spectrogram.spectChanSelectBot.cp5_channelCheckboxes.get(CheckBox.class, "Spectrogram_Channels_Bot").deactivateAll(); - if (loadSpectActiveChanBot.size() > 0) { - int activeChanCounter = 0; - for (int i = 0; i < nchan; i++) { - if (activeChanCounter < loadSpectActiveChanBot.size()) { - //subtract 1 because cp5 starts count from 0 - if (i == (loadSpectActiveChanBot.get(activeChanCounter) - 1)) { - w_spectrogram.spectChanSelectBot.cp5_channelCheckboxes.get(CheckBox.class, "Spectrogram_Channels_Bot").activate(i); - activeChanCounter++; - } - } - } - } - } catch (Exception e) { - println("Settings: Exception caught applying ssvep settings" + e); - } - verbosePrint("Settings: SSVEP Active Channels: " + loadSSVEPActiveChans); ///////////Apply Networking Settings //Update protocol with loaded value Protocol(nwProtocolLoad); //Update dropdowns and textfields in the Networking widget with loaded values w_networking.cp5_widget.getController("Protocol").getCaptionLabel().setText(nwProtocolArray[nwProtocolLoad]); //Reference the dropdown from the appropriate widget + w_networking.cp5_networking.get(Toggle.class, "filter1").setState(false); + w_networking.cp5_networking.get(Toggle.class, "filter2").setState(false); + w_networking.cp5_networking.get(Toggle.class, "filter3").setState(false); + w_networking.cp5_networking.get(Toggle.class, "filter4").setState(false); switch (nwProtocolLoad) { case 3: //Apply OSC if loaded println("Apply OSC Networking Mode"); @@ -1376,10 +939,10 @@ class SoftwareSettings { w_networking.cp5_networking.get(Textfield.class, "OSC_address2").setText(nwOscAddress2Load); w_networking.cp5_networking.get(Textfield.class, "OSC_address3").setText(nwOscAddress3Load); w_networking.cp5_networking.get(Textfield.class, "OSC_address4").setText(nwOscAddress4Load); - w_networking.cp5_networking.get(RadioButton.class, "filter1").activate(nwOscFilter1Load); - w_networking.cp5_networking.get(RadioButton.class, "filter2").activate(nwOscFilter2Load); - w_networking.cp5_networking.get(RadioButton.class, "filter3").activate(nwOscFilter3Load); - w_networking.cp5_networking.get(RadioButton.class, "filter4").activate(nwOscFilter4Load); + w_networking.cp5_networking.get(Toggle.class, "filter1").setState(nwOscFilter1Load); + w_networking.cp5_networking.get(Toggle.class, "filter2").setState(nwOscFilter2Load); + w_networking.cp5_networking.get(Toggle.class, "filter3").setState(nwOscFilter3Load); + w_networking.cp5_networking.get(Toggle.class, "filter4").setState(nwOscFilter4Load); break; case 2: //Apply UDP if loaded println("Apply UDP Networking Mode"); @@ -1395,9 +958,9 @@ class SoftwareSettings { w_networking.cp5_networking.get(Textfield.class, "UDP_port1").setText(nwUdpPort1Load); w_networking.cp5_networking.get(Textfield.class, "UDP_port2").setText(nwUdpPort2Load); w_networking.cp5_networking.get(Textfield.class, "UDP_port3").setText(nwUdpPort3Load); - w_networking.cp5_networking.get(RadioButton.class, "filter1").activate(nwUdpFilter1Load); - w_networking.cp5_networking.get(RadioButton.class, "filter2").activate(nwUdpFilter2Load); - w_networking.cp5_networking.get(RadioButton.class, "filter3").activate(nwUdpFilter3Load); + w_networking.cp5_networking.get(Toggle.class, "filter1").setState(nwUdpFilter1Load); + w_networking.cp5_networking.get(Toggle.class, "filter2").setState(nwUdpFilter2Load); + w_networking.cp5_networking.get(Toggle.class, "filter3").setState(nwUdpFilter3Load); break; case 1: //Apply LSL if loaded println("Apply LSL Networking Mode"); @@ -1413,12 +976,9 @@ class SoftwareSettings { w_networking.cp5_networking.get(Textfield.class, "LSL_type1").setText(nwLSLType1Load); w_networking.cp5_networking.get(Textfield.class, "LSL_type2").setText(nwLSLType2Load); w_networking.cp5_networking.get(Textfield.class, "LSL_type3").setText(nwLSLType3Load); - w_networking.cp5_networking.get(Textfield.class, "LSL_numchan1").setText(nwLSLNumChan1Load); - w_networking.cp5_networking.get(Textfield.class, "LSL_numchan2").setText(nwLSLNumChan2Load); - w_networking.cp5_networking.get(Textfield.class, "LSL_numchan3").setText(nwLSLNumChan3Load); - w_networking.cp5_networking.get(RadioButton.class, "filter1").activate(nwLSLFilter1Load); - w_networking.cp5_networking.get(RadioButton.class, "filter2").activate(nwLSLFilter2Load); - w_networking.cp5_networking.get(RadioButton.class, "filter3").activate(nwLSLFilter3Load); + w_networking.cp5_networking.get(Toggle.class, "filter1").setState(nwLSLFilter1Load); + w_networking.cp5_networking.get(Toggle.class, "filter2").setState(nwLSLFilter2Load); + w_networking.cp5_networking.get(Toggle.class, "filter3").setState(nwLSLFilter3Load); break; case 0: //Apply Serial if loaded println("Apply Serial Networking Mode"); @@ -1426,7 +986,7 @@ class SoftwareSettings { w_networking.cp5_networking_dropdowns.get(ScrollableList.class, "dataType1").setValue(nwDataType1); //Set value in backend w_networking.cp5_networking_baudRate.getController("baud_rate").getCaptionLabel().setText(nwBaudRatesArray[nwSerialBaudRateLoad]); //Set text w_networking.cp5_networking_baudRate.get(ScrollableList.class, "baud_rate").setValue(nwSerialBaudRateLoad); //Set value in backend - w_networking.cp5_networking.get(RadioButton.class, "filter1").activate(nwSerialFilter1Load); + w_networking.cp5_networking.get(Toggle.class, "filter1").setState(nwSerialFilter1Load); //Look for the portName in the dropdown list int listSize = w_networking.cp5_networking_portName.get(ScrollableList.class, "port_name").getItems().size(); @@ -1460,118 +1020,7 @@ class SoftwareSettings { w_timeSeries.cp5_widget.getController("VertScale_TS").getCaptionLabel().setText(tsVertScaleArray[loadTimeSeriesVertScale]); //changes front-end Duration(loadTimeSeriesHorizScale); w_timeSeries.cp5_widget.getController("Duration").getCaptionLabel().setText(tsHorizScaleArray[loadTimeSeriesHorizScale]); - - //Make a JSON object to load channel setting array - JSONArray loadTimeSeriesJSONArray = loadTimeSeriesSettings.getJSONArray("channelSettings"); - - //Case for loading time series settings in Live Data mode - if (eegDataSource == DATASOURCE_CYTON) { - //get the channel settings first for only the number of channels being used - for (int i = 0; i < numChanloaded; i++) { - JSONObject loadTSChannelSettings = loadTimeSeriesJSONArray.getJSONObject(i); - int channel = loadTSChannelSettings.getInt("Channel_Number") - 1; //when using with channelSettingsValues, will need to subtract 1 - int active = loadTSChannelSettings.getInt("Active"); - int gainSetting = loadTSChannelSettings.getInt("PGA Gain"); - int inputType = loadTSChannelSettings.getInt("Input Type"); - int biasSetting = loadTSChannelSettings.getInt("Bias"); - int srb2Setting = loadTSChannelSettings.getInt("SRB2"); - int srb1Setting = loadTSChannelSettings.getInt("SRB1"); - println("Ch " + channel + ", " + - channelsActiveArray[active] + ", " + - gainSettingsArray[gainSetting] + ", " + - inputTypeArray[inputType] + ", " + - biasIncludeArray[biasSetting] + ", " + - srb2SettingArray[srb2Setting] + ", " + - srb1SettingArray[srb1Setting]); - - //Use channelSettingValues variable to store these settings once they are loaded from JSON file. Update occurs in hwSettingsController - channelSettingValues[i][0] = (char)(active + '0'); - if (active == 0) { - if (eegDataSource == DATASOURCE_GANGLION) { - activateChannel(channel);// power down == false, set color to vibrant - } - w_timeSeries.channelBars[i].isOn = true; - w_timeSeries.channelBars[i].onOffButton.setColorNotPressed(channelColors[(channel)%8]); - } else { - if (eegDataSource == DATASOURCE_GANGLION) { - deactivateChannel(channel); // power down == true, set color to dark gray, indicating power down - } - w_timeSeries.channelBars[i].isOn = false; // deactivate it - w_timeSeries.channelBars[i].onOffButton.setColorNotPressed(color(50)); - } - //Set gain - channelSettingValues[i][1] = (char)(gainSetting + '0'); //Convert int to char by adding the gainSetting to ASCII char '0' - //Set inputType - channelSettingValues[i][2] = (char)(inputType + '0'); - //Set Bias - channelSettingValues[i][3] = (char)(biasSetting + '0'); - //Set SRB2 - channelSettingValues[i][4] = (char)(srb2Setting + '0'); - //Set SRB1 - channelSettingValues[i][5] = (char)(srb1Setting + '0'); - } //end case for all channels - - loadErrorTimerStart = millis(); - for (int i = 0; i < slnchan; i++) { //For all time series channels... - try { - cyton.writeChannelSettings(i, channelSettingValues); //Write the channel settings to the board! - } catch (RuntimeException e) { - verbosePrint("Runtime Error when trying to write channel settings to cyton..."); - } - if (checkForSuccessTS > 0) { // If we receive a return code... - println("Return code: " + checkForSuccessTS); - //when successful, iterate to next channel(i++) and set Check to null - if (checkForSuccessTS == RESP_SUCCESS) { - // i++; - checkForSuccessTS = 0; - } - - //This catches the error when there is difficulty connecting to Cyton. Tested by using dongle with Cyton turned off! - int timeElapsed = millis() - loadErrorTimerStart; - if (timeElapsed >= loadErrorTimeWindow) { //If the time window (3.8 seconds) has elapsed... - println("FAILED TO APPLY SETTINGS TO CYTON WITHIN TIME WINDOW. STOPPING SYSTEM."); - loadErrorCytonEvent = true; //Set true because an error has occured - haltSystem(); //Halt the system to stop the initialization process - return; - } - } - //delay(10);// Works on 8 chan sometimes - delay(250); // Works on 8 and 16 channels 3/3 trials applying settings to all channels. - //Tested by setting gain 1x and loading 24x. - } - loadErrorCytonEvent = false; - } //end Cyton case - - //////////Case for loading Time Series settings when in Ganglion, Synthetic, or Playback data mode - if (eegDataSource == DATASOURCE_SYNTHETIC || eegDataSource == DATASOURCE_PLAYBACKFILE || eegDataSource == DATASOURCE_GANGLION) { - //get the channel settings first for only the number of channels being used - if (eegDataSource == DATASOURCE_GANGLION) numChanloaded = 4; - for (int i = 0; i < numChanloaded; i++) { - JSONObject loadTSChannelSettings = loadTimeSeriesJSONArray.getJSONObject(i); - //int channel = loadTSChannelSettings.getInt("Channel_Number") - 1; //when using with channelSettingsValues, will need to subtract 1 - int active = loadTSChannelSettings.getInt("Active"); - //println("Ch " + channel + ", " + channelsActiveArray[active]); - if (active == 1) { - if (eegDataSource == DATASOURCE_GANGLION) { //if using Ganglion, send the appropriate command to the hub to activate a channel - println("Ganglion: loadApplyChannelSettings(): activate: sending " + command_activate_channel[i]); - hub.sendCommand(command_activate_channel[i]); - w_timeSeries.hsc.powerUpChannel(i); - } - w_timeSeries.channelBars[i].isOn = true; - channelSettingValues[i][0] = '0'; - w_timeSeries.channelBars[i].onOffButton.setColorNotPressed(channelColors[(i)%8]); - } else { - if (eegDataSource == DATASOURCE_GANGLION) { //if using Ganglion, send the appropriate command to the hub to activate a channel - println("Ganglion: loadApplyChannelSettings(): deactivate: sending " + command_deactivate_channel[i]); - hub.sendCommand(command_deactivate_channel[i]); - w_timeSeries.hsc.powerDownChannel(i); - } - w_timeSeries.channelBars[i].isOn = false; // deactivate it - channelSettingValues[i][0] = '1'; - w_timeSeries.channelBars[i].onOffButton.setColorNotPressed(color(50)); - } - } - } //end of Ganglion/Playback/Synthetic case + } //end loadApplyTimeSeriesSettings /** @@ -1580,7 +1029,7 @@ class SoftwareSettings { * Output Success message to bottom of GUI when done */ void clearAll() { - for (File file: new File(settingsPath).listFiles()) + for (File file: new File(directoryManager.getSettingsPath()).listFiles()) if (!file.isDirectory()) file.delete(); controlPanel.recentPlaybackBox.cp5_recentPlayback_dropdown.get(ScrollableList.class, "recentFiles").clear(); @@ -1595,7 +1044,7 @@ class SoftwareSettings { * @returns {String} - filePath or Error if mode not specified correctly */ String getPath(String _mode, int dataSource, int _nchan) { - String filePath = settingsPath; + String filePath = directoryManager.getSettingsPath(); String[] fileNames = new String[7]; if (_mode.equals("Default")) { fileNames = defaultSettingsFiles; @@ -1611,15 +1060,17 @@ class SoftwareSettings { fileNames[1]; } else if (dataSource == DATASOURCE_GANGLION) { filePath += fileNames[2]; - } else if (dataSource == DATASOURCE_PLAYBACKFILE) { + } else if (dataSource == DATASOURCE_NOVAXR) { filePath += fileNames[3]; + } else if (dataSource == DATASOURCE_PLAYBACKFILE) { + filePath += fileNames[4]; } else if (dataSource == DATASOURCE_SYNTHETIC) { if (_nchan == NCHAN_GANGLION) { - filePath += fileNames[4]; - } else if (_nchan == NCHAN_CYTON) { filePath += fileNames[5]; - } else { + } else if (_nchan == NCHAN_CYTON) { filePath += fileNames[6]; + } else { + filePath += fileNames[7]; } } } @@ -1627,43 +1078,11 @@ class SoftwareSettings { } void initCheckPointFive() { - //Prepare the data mode and version, if needed, to be printed at init checkpoint 5 below - String firmwareToPrint = ""; - String dataModeVersionToPrint = controlEventDataSource; - if (eegDataSource == DATASOURCE_CYTON) { - if (!settings.loadErrorCytonEvent) { - firmwareToPrint = " " + hub.firmwareVersion + ")"; - } else { - firmwareToPrint = "v.?)"; - } - dataModeVersionToPrint = controlEventDataSource.replace(")", " "); - dataModeVersionToPrint += firmwareToPrint; - } - - //Output messages when Loading settings is complete - if (chanNumError == false - && dataSourceError == false - && errorUserSettingsNotFound == false - && loadErrorCytonEvent == false) { - verbosePrint("OpenBCI_GUI: initSystem: -- Init 5 -- " + "Settings Loaded! " + millis()); //Print success to console - if (eegDataSource == DATASOURCE_SYNTHETIC || eegDataSource == DATASOURCE_PLAYBACKFILE) { - outputSuccess("Settings Loaded!"); //Show success message for loading User Settings - } - } else if (chanNumError) { - verbosePrint("OpenBCI_GUI: initSystem: -- Init 5 -- " + "Load settings error: Invalid number of channels in JSON " + millis()); //Print the error to console - output("The new data source is " + dataModeVersionToPrint + " and NCHAN = [" + nchan + "]. Channel number error: Default Settings Loaded."); //Show a normal message for loading Default Settings - } else if (dataSourceError) { - verbosePrint("OpenBCI_GUI: initSystem: -- Init 5 -- " + "Load settings error: Invalid data source " + millis()); //Print the error to console - output("The new data source is " + dataModeVersionToPrint + " and NCHAN = [" + nchan + "]. Data source error: Default Settings Loaded."); //Show a normal message for loading Default Settings - } else if (errorUserSettingsNotFound) { - verbosePrint("OpenBCI_GUI: initSystem: -- Init 5 -- " + "Load settings error: File not found. " + millis()); //Print the error to console - output("The new data source is " + dataModeVersionToPrint + " and NCHAN = [" + nchan + "]. User Settings Error: Default Settings Loaded."); //Show a normal message for loading Default Settings + if (eegDataSource == DATASOURCE_NOVAXR) { + outputSuccess("NovaXR Firmware == " + "WIP"); } else { - verbosePrint("OpenBCI_GUI: initSystem: -- Init 5 -- " + "Load settings error: Connection Error: Failed to apply channel settings to Cyton" + millis()); //Print the error to console - outputError(dataModeVersionToPrint + " and NCHAN = [" + nchan + "]. Connection Error: Channel settings failed to apply to Cyton."); //Show a normal message for loading Default Settings + outputSuccess("Session started!"); } - //At this point, either User or Default settings have been Loaded. Use this var to keep track of this. - settingsLoaded = true; } void loadKeyPressed() { @@ -1698,7 +1117,7 @@ class SoftwareSettings { if (f.delete()) { outputError("Found old/broken GUI settings. Please reconfigure the GUI and save new settings."); } else { - outputError("SoftwareSettings: Error deleting old/broken settings file..."); + outputError("SessionSettings: Error deleting old/broken settings file..."); } } } @@ -1739,15 +1158,14 @@ class SoftwareSettings { File f = new File(defaultSettingsFileToLoad); if (f.exists()) { if (f.delete()) { - println("SoftwareSettings: Old/Broken Default Settings file succesfully deleted."); + println("SessionSettings: Old/Broken Default Settings file succesfully deleted."); } else { - println("SoftwareSettings: Error deleting Default Settings file..."); + println("SessionSettings: Error deleting Default Settings file..."); } } } } - } //end of Software Settings class void imposeMinimumGUIDimensions() { @@ -1767,9 +1185,9 @@ void imposeMinimumGUIDimensions() { // Select file to save custom settings using dropdown in TopNav.pde void saveConfigFile(File selection) { if (selection == null) { - println("SoftwareSettings: saveConfigFile: Window was closed or the user hit cancel."); + println("SessionSettings: saveConfigFile: Window was closed or the user hit cancel."); } else { - println("SoftwareSettings: saveConfigFile: User selected " + selection.getAbsolutePath()); + println("SessionSettings: saveConfigFile: User selected " + selection.getAbsolutePath()); settings.saveDialogName = selection.getAbsolutePath(); settings.save(settings.saveDialogName); //save current settings to JSON file in SavedData outputSuccess("Settings Saved! The GUI will now load with these settings. Click \"Default\" to revert to factory settings."); //print success message to screen @@ -1779,9 +1197,9 @@ void saveConfigFile(File selection) { // Select file to load custom settings using dropdown in TopNav.pde void loadConfigFile(File selection) { if (selection == null) { - println("SoftwareSettings: loadConfigFile: Window was closed or the user hit cancel."); + println("SessionSettings: loadConfigFile: Window was closed or the user hit cancel."); } else { - println("SoftwareSettings: loadConfigFile: User selected " + selection.getAbsolutePath()); + println("SessionSettings: loadConfigFile: User selected " + selection.getAbsolutePath()); //output("You have selected \"" + selection.getAbsolutePath() + "\" to Load custom settings."); settings.loadDialogName = selection.getAbsolutePath(); try { @@ -1793,7 +1211,7 @@ void loadConfigFile(File selection) { outputSuccess("Settings Loaded!"); } } catch (Exception e) { - println("SoftwareSettings: Incompatible settings file or other error"); + println("SessionSettings: Incompatible settings file or other error"); if (settings.chanNumError == true) { outputError("Settings Error: Channel Number Mismatch Detected"); } else if (settings.dataSourceError == true) { diff --git a/OpenBCI_GUI/SmoothingBoard.pde b/OpenBCI_GUI/SmoothingBoard.pde new file mode 100644 index 000000000..9106128cf --- /dev/null +++ b/OpenBCI_GUI/SmoothingBoard.pde @@ -0,0 +1,6 @@ +interface SmoothingCapableBoard { + + public void setSmoothingActive(boolean active); + + public boolean getSmoothingActive(); +}; diff --git a/OpenBCI_GUI/TopNav.pde b/OpenBCI_GUI/TopNav.pde index 9b1ad8d50..dc08a6126 100644 --- a/OpenBCI_GUI/TopNav.pde +++ b/OpenBCI_GUI/TopNav.pde @@ -16,21 +16,22 @@ TopNav topNav; class TopNav { - Button controlPanelCollapser; - Button fpsButton; - Button debugButton; + Button_obci controlPanelCollapser; + Button_obci fpsButton; + Button_obci debugButton; - Button stopButton; + Button_obci stopButton; - Button filtBPButton; - Button filtNotchButton; + Button_obci filtBPButton; + Button_obci filtNotchButton; + Button_obci smoothingButton; - Button tutorialsButton; - Button shopButton; - Button issuesButton; - Button updateGuiVersionButton; - Button layoutButton; - Button configButton; + Button_obci tutorialsButton; + Button_obci shopButton; + Button_obci issuesButton; + Button_obci updateGuiVersionButton; + Button_obci layoutButton; + Button_obci configButton; LayoutSelector layoutSelector; TutorialSelector tutorialSelector; @@ -46,12 +47,12 @@ class TopNav { //constructor TopNav() { int w = 256; - controlPanelCollapser = new Button(3, 3, w, 26, "System Control Panel", fontInfo.buttonLabel_size); + controlPanelCollapser = new Button_obci(3, 3, w, 26, "System Control Panel", fontInfo.buttonLabel_size); controlPanelCollapser.setFont(h3, 16); controlPanelCollapser.setIsActive(true); controlPanelCollapser.isDropdownButton = true; - fpsButton = new Button(controlPanelCollapser.but_x + controlPanelCollapser.but_dx + 3, 3, 73, 26, "XX" + " fps", fontInfo.buttonLabel_size); + fpsButton = new Button_obci(controlPanelCollapser.but_x + controlPanelCollapser.but_dx + 3, 3, 73, 26, "XX" + " fps", fontInfo.buttonLabel_size); if (frameRateCounter==0) { fpsButton.setString("24 fps"); } @@ -67,33 +68,33 @@ class TopNav { fpsButton.setFont(h3, 16); fpsButton.setHelpText("If you're having latency issues, try adjusting the frame rate and see if it helps!"); - //highRezButton = new Button(3+3+w+73+3, 3, 26, 26, "XX", fontInfo.buttonLabel_size); + //highRezButton = new Button_obci(3+3+w+73+3, 3, 26, 26, "XX", fontInfo.buttonLabel_size); controlPanelCollapser.setFont(h3, 16); //top right buttons from right to left - debugButton = new Button(width - 33 - 3, 3, 33, 26, " ", fontInfo.buttonLabel_size); + debugButton = new Button_obci(width - 33 - 3, 3, 33, 26, " ", fontInfo.buttonLabel_size); debugButton.setHelpText("Click to open the Console Log window."); - tutorialsButton = new Button(debugButton.but_x - 80 - 3, 3, 80, 26, "Help", fontInfo.buttonLabel_size); + tutorialsButton = new Button_obci(debugButton.but_x - 80 - 3, 3, 80, 26, "Help", fontInfo.buttonLabel_size); tutorialsButton.setFont(h3, 16); tutorialsButton.setHelpText("Click to find links to helpful online tutorials and getting started guides. Also, check out how to create custom widgets for the GUI!"); - issuesButton = new Button(tutorialsButton.but_x - 80 - 3, 3, 80, 26, "Issues", fontInfo.buttonLabel_size); + issuesButton = new Button_obci(tutorialsButton.but_x - 80 - 3, 3, 80, 26, "Issues", fontInfo.buttonLabel_size); issuesButton.setHelpText("If you have suggestions or want to share a bug you've found, please create an issue on the GUI's Github repo!"); issuesButton.setURL("https://github.com/OpenBCI/OpenBCI_GUI/issues"); issuesButton.setFont(h3, 16); - shopButton = new Button(issuesButton.but_x - 80 - 3, 3, 80, 26, "Shop", fontInfo.buttonLabel_size); + shopButton = new Button_obci(issuesButton.but_x - 80 - 3, 3, 80, 26, "Shop", fontInfo.buttonLabel_size); shopButton.setHelpText("Head to our online store to purchase the latest OpenBCI hardware and accessories."); shopButton.setURL("http://shop.openbci.com/"); shopButton.setFont(h3, 16); - configButton = new Button(width - 70 - 3, 35, 70, 26, "Settings", fontInfo.buttonLabel_size); + configButton = new Button_obci(width - 70 - 3, 35, 70, 26, "Settings", fontInfo.buttonLabel_size); configButton.setHelpText("Save and Load GUI Settings! Click Default to revert to factory settings."); configButton.setFont(h4, 14); //Lookup and check the local GUI version against the latest Github release - updateGuiVersionButton = new Button(shopButton.but_x - 80 - 3, 3, 80, 26, "Update", fontInfo.buttonLabel_size); + updateGuiVersionButton = new Button_obci(shopButton.but_x - 80 - 3, 3, 80, 26, "Update", fontInfo.buttonLabel_size); updateGuiVersionButton.setFont(h3, 16); checkInternetFetchGithubData(); @@ -106,21 +107,27 @@ class TopNav { } void initSecondaryNav() { - stopButton = new Button(3, 35, 170, 26, stopButton_pressToStart_txt, fontInfo.buttonLabel_size); + stopButton = new Button_obci(3, 35, 170, 26, stopButton_pressToStart_txt, fontInfo.buttonLabel_size); stopButton.setFont(h4, 14); stopButton.setColorNotPressed(color(184, 220, 105)); stopButton.setHelpText("Press this button to Stop/Start the data stream. Or press "); - filtNotchButton = new Button(7 + stopButton.but_dx, 35, 70, 26, "Notch\n" + dataProcessing.getShortNotchDescription(), fontInfo.buttonLabel_size); + filtNotchButton = new Button_obci(7 + stopButton.but_dx, 35, 70, 26, "Notch\n" + dataProcessing.getShortNotchDescription(), fontInfo.buttonLabel_size); filtNotchButton.setFont(p5, 12); filtNotchButton.setHelpText("Here you can adjust the Notch Filter that is applied to all \"Filtered\" data."); - filtBPButton = new Button(11 + stopButton.but_dx + 70, 35, 70, 26, "BP Filt\n" + dataProcessing.getShortFilterDescription(), fontInfo.buttonLabel_size); + filtBPButton = new Button_obci(11 + stopButton.but_dx + 70, 35, 70, 26, "BP Filt\n" + dataProcessing.getShortFilterDescription(), fontInfo.buttonLabel_size); filtBPButton.setFont(p5, 12); filtBPButton.setHelpText("Here you can adjust the Band Pass Filter that is applied to all \"Filtered\" data."); + if (currentBoard instanceof SmoothingCapableBoard) { + smoothingButton = new Button_obci(filtBPButton.but_x + filtBPButton.but_dx + 4, 35, 70, 26, getSmoothingString(), fontInfo.buttonLabel_size); + smoothingButton.setFont(p5, 12); + smoothingButton.setHelpText("Click here to turn data smoothing on or off."); + } + //right to left in top right (secondary nav) - layoutButton = new Button(width - 3 - 60, 35, 60, 26, "Layout", fontInfo.buttonLabel_size); + layoutButton = new Button_obci(width - 3 - 60, 35, 60, 26, "Layout", fontInfo.buttonLabel_size); layoutButton.setHelpText("Here you can alter the overall layout of the GUI, allowing for different container configurations with more or less widgets."); layoutButton.setFont(h4, 14); @@ -184,6 +191,11 @@ class TopNav { filtBPButton.textColorNotActive = color(bgColor); filtNotchButton.textColorNotActive = color(bgColor); layoutButton.textColorNotActive = color(bgColor); + + if (currentBoard instanceof SmoothingCapableBoard) { + smoothingButton.textColorNotActive = color(bgColor); + smoothingButton.setColorNotPressed(color(255)); + } } else if (colorScheme == COLOR_SCHEME_ALTERNATIVE_A) { filtBPButton.setColorNotPressed(color(57, 128, 204)); filtNotchButton.setColorNotPressed(color(57, 128, 204)); @@ -192,6 +204,11 @@ class TopNav { filtBPButton.textColorNotActive = color(255); filtNotchButton.textColorNotActive = color(255); layoutButton.textColorNotActive = color(255); + + if (currentBoard instanceof SmoothingCapableBoard) { + smoothingButton.setColorNotPressed(color(57, 128, 204)); + smoothingButton.textColorNotActive = color(255); + } } } @@ -254,11 +271,15 @@ class TopNav { popStyle(); + //Draw these buttons during a Session if (systemMode == SYSTEMMODE_POSTINIT) { stopButton.draw(); filtBPButton.draw(); filtNotchButton.draw(); layoutButton.draw(); + if (currentBoard instanceof SmoothingCapableBoard) { + smoothingButton.draw(); + } } controlPanelCollapser.draw(); @@ -301,15 +322,17 @@ class TopNav { if (systemMode >= SYSTEMMODE_POSTINIT) { if (stopButton.isMouseHere()) { stopButton.setIsActive(true); - stopButtonWasPressed(); } if (filtBPButton.isMouseHere()) { filtBPButton.setIsActive(true); - incrementFilterConfiguration(); } if (topNav.filtNotchButton.isMouseHere()) { filtNotchButton.setIsActive(true); - incrementNotchConfiguration(); + } + if (currentBoard instanceof SmoothingCapableBoard) { + if (smoothingButton.isMouseHere()) { + smoothingButton.setIsActive(true); + } } if (layoutButton.isMouseHere()) { layoutButton.setIsActive(true); @@ -379,7 +402,7 @@ class TopNav { toggleFrameRate(); } if (debugButton.isMouseHere() && debugButton.isActive()) { - thread("openConsole"); + ConsoleWindow.display(); } if (tutorialsButton.isMouseHere() && tutorialsButton.isActive()) { @@ -409,6 +432,25 @@ class TopNav { } if (systemMode == SYSTEMMODE_POSTINIT) { + if (stopButton.isMouseHere() && stopButton.isActive()) { + stopButtonWasPressed(); + } + if (filtBPButton.isMouseHere() && filtBPButton.isActive()) { + incrementFilterConfiguration(); + } + if (filtNotchButton.isMouseHere() && filtNotchButton.isActive()) { + filtNotchButton.setIsActive(true); + incrementNotchConfiguration(); + } + if (currentBoard instanceof SmoothingCapableBoard) { + if (smoothingButton.isMouseHere() && smoothingButton.isActive()) { + smoothingButton.setIsActive(true); + //toggle data smoothing on mousePress for capable boards + SmoothingCapableBoard smoothBoard = (SmoothingCapableBoard)currentBoard; + smoothBoard.setSmoothingActive(!smoothBoard.getSmoothingActive()); + smoothingButton.setString(getSmoothingString()); + } + } if (!tutorialSelector.isVisible) { //make sure that you can't open the layout selector accidentally if (layoutButton.isMouseHere() && layoutButton.isActive()) { layoutSelector.toggleVisibility(); @@ -421,6 +463,10 @@ class TopNav { filtBPButton.setIsActive(false); filtNotchButton.setIsActive(false); layoutButton.setIsActive(false); + + if (currentBoard instanceof SmoothingCapableBoard) { + smoothingButton.setIsActive(false); + } } fpsButton.setIsActive(false); @@ -548,6 +594,10 @@ class TopNav { updateGuiVersionButton.setHelpText("Connect to internet to check GUI version. -- Local: " + localGUIVersionString); } } + + private String getSmoothingString() { + return ((SmoothingCapableBoard)currentBoard).getSmoothingActive() ? "Smoothing\nOn" : "Smoothing\nOff"; + } } //=============== OLD STUFF FROM Gui_Manger.pde ===============// @@ -573,7 +623,7 @@ class LayoutSelector { int x, y, w, h, margin, b_w, b_h; boolean isVisible; - ArrayList